fork这个瞬间一定是会阻塞主线程的。注意,fork时并不会一次性拷贝所有内存数据给子进程,,fork采用操作系统提供的写实复制(Copy On Write)机制,就是为了避免一次性拷贝大量内存数据给子进程造成的长时间阻塞问题,但fork子进程需要拷贝进程必要的数据结构,其中有一项就是拷贝内存页表(虚拟内存和物理内存的映射索引表),这个拷贝过程会消耗大量CPU资源,拷贝完成之前整个进程是会阻塞的,阻塞时间取决于整个实例的内存大小,实例越大,内存页表越大,fork阻塞时间越久。
/* 将命令传播给 AOF 和 Slaves. * * flags are an xor between: * + PROPAGATE_NONE (no propagation of command at all) * + PROPAGATE_AOF (propagate into the AOF file if is enabled) * + PROPAGATE_REPL (propagate into the replication link) * */ voidpropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags) { ······ if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF) // 将命令传递给 aof feedAppendOnlyFile(cmd,dbid,argv,argc); if (flags & PROPAGATE_REPL) replicationFeedSlaves(server.slaves,dbid,argv,argc); }
/* 下面这一大段if else,主要是将命令做一定的转换,方便 redis 处理,暂时可以不用追究其中的细节 */ if (cmd->proc == expireCommand || cmd->proc == pexpireCommand || cmd->proc == expireatCommand) { // 将 EXPIRE/PEXPIRE/EXPIREAT 转换成 PEXPIREAT // 这几个 expire 命令,其实都是调用的 expireGenericCommand() 方法,只不过是参数有区别罢了 buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]); } elseif (cmd->proc == setCommand && argc > 3) { robj *pxarg = NULL; /* When SET is used with EX/PX argument setGenericCommand propagates them with PX millisecond argument. * So since the command arguments are re-written there, we can rely here on the index of PX being 3. */ if (!strcasecmp(argv[3]->ptr, "px")) { pxarg = argv[4]; } /* For AOF we convert SET key value relative time in milliseconds to SET key value absolute time in * millisecond. Whenever the condition is true it implies that original SET has been transformed * to SET PX with millisecond time argument so we do not need to worry about unit here.*/ if (pxarg) { robj *millisecond = getDecodedObject(pxarg); longlong when = strtoll(millisecond->ptr,NULL,10); when += mstime();
/* This function gets called every time Redis is entering the * main loop of the event driven library, that is, before to sleep * for ready file descriptors. * * 注意,一般情况下,此方法的调用方有两个: * 1. aeMain - 主事件循环 * 2. processEventsWhileBlocked - Process clients during RDB/AOF load * 在调用方是 processEventsWhileBlocked 的时候,有些工作是不需要做的,比如淘汰过期 key */ voidbeforeSleep(struct aeEventLoop *eventLoop){ UNUSED(eventLoop);
····
/* Handle precise timeouts of blocked clients. */ handleBlockedClientsTimeout();
/* We should handle pending reads clients ASAP after event loop. */ handleClientsWithPendingReadsUsingThreads();
/* Handle TLS pending data. (must be done before flushAppendOnlyFile) */ tlsProcessPendingData();
/* If tls still has pending unread data don't sleep at all. */ aeSetDontWait(server.el, tlsHasPendingData());
······
/* 如果开启了 aof 的话,在这里执行刷盘操作 */ if (server.aof_state == AOF_ON) flushAppendOnlyFile(0);
/* Handle writes with pending output buffers. */ handleClientsWithPendingWritesUsingThreads();
/* Close clients that need to be closed asynchronous */ freeClientsInAsyncFreeQueue(); }
if (sdslen(server.aof_buf) == 0) { /* Check if we need to do fsync even the aof buffer is empty, * because previously in AOF_FSYNC_EVERYSEC mode, fsync is * called only when aof buffer is not empty, so if users * stop write commands before fsync called in one second, * the data in page cache cannot be flushed in time. */ if (server.aof_fsync == AOF_FSYNC_EVERYSEC && server.aof_fsync_offset != server.aof_current_size && server.unixtime > server.aof_last_fsync && !(sync_in_progress = aofFsyncInProgress())) { goto try_fsync; } else { return; } }
if (server.aof_fsync == AOF_FSYNC_EVERYSEC) sync_in_progress = aofFsyncInProgress();
if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) { /* With this append fsync policy we do background fsyncing. * If the fsync is still in progress we can try to delay * the write for a couple of seconds. */ if (sync_in_progress) { if (server.aof_flush_postponed_start == 0) { /* No previous write postponing, remember that we are * postponing the flush and return. */ server.aof_flush_postponed_start = server.unixtime; return; } elseif (server.unixtime - server.aof_flush_postponed_start < 2) { /* We were already waiting for fsync to finish, but for less * than two seconds this is still ok. Postpone again. */ return; } /* Otherwise fall trough, and go write since we can't wait * over two seconds. */ server.aof_delayed_fsync++; serverLog(LL_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis."); } } /* 执行 write 操作. 它的原子性由操作系统保证. * 当然,如果出现像断电这样的不可抗现象,那么 AOF 文件也是可能会出现问题的 * 这时候就可以用 redis-check-aof 程序来进行修复 */
/* 测试用:aof_flush_sleep:Micros to sleep before flush. (used by tests) */ if (server.aof_flush_sleep && sdslen(server.aof_buf)) { usleep(server.aof_flush_sleep); }
/* 把 buffer 中的内容写 aof 文件的内存缓冲区 */ /* 下面这段是 aofWrite 的方法注释,没太看懂,后面有时间再深究下*/ /* This is a wrapper to the write syscall in order to retry on short writes * or if the syscall gets interrupted. It could look strange that we retry * on short writes given that we are writing to a block device: normally if * the first call is short, there is a end-of-space condition, so the next * is likely to fail. However apparently in modern systems this is no longer * true, and in general it looks just more resilient to retry the write. If * there is an actual error condition we'll get it at the next try. */ nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
/* Limit logging rate to 1 line per AOF_WRITE_LOG_ERROR_RATE seconds. */ if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) { can_log = 1; last_write_error_log = server.unixtime; }
/* 记录错误日志 */ if (nwritten == -1) { // buffer 中的内容一个字都没写进去 if (can_log) { serverLog(LL_WARNING,"Error writing to the AOF file: %s", strerror(errno)); server.aof_last_write_errno = errno; } } else { // buffer 中的内容没写完 if (can_log) { serverLog(LL_WARNING,"Short write while writing to " "the AOF file: (nwritten=%lld, " "expected=%lld)", (longlong)nwritten, (longlong)sdslen(server.aof_buf)); }
// 尝试移除(由操作系统造成的,比如断电)新追加的不完整内容 if (ftruncate(server.aof_fd, server.aof_current_size) == -1) { if (can_log) { serverLog(LL_WARNING, "Could not remove short write " "from the append-only file. Redis may refuse " "to load the AOF the next time it starts. " "ftruncate: %s", strerror(errno)); } } else { /* 如果 ftruncate() 移除成功,那就当本次 write error 没发生过,下个循环再重试。 * -1 since there is no longer partial data into the AOF. */ nwritten = -1; } server.aof_last_write_errno = ENOSPC; }
if (nwritten > 0) { server.aof_current_size += nwritten; sdsrange(server.aof_buf,nwritten,-1); } return; /* We'll try again on the next call... */ } } else { /* Successful write(2). If AOF was in error state, restore the * OK state and log the event. */ if (server.aof_last_write_status == C_ERR) { serverLog(LL_WARNING, "AOF write error looks solved, Redis can write again."); server.aof_last_write_status = C_OK; } } server.aof_current_size += nwritten;
/* Perform the fsync if needed. */ if (server.aof_fsync == AOF_FSYNC_ALWAYS) { // 既然是 AOF_FSYNC_ALWAYS,那么每次写完 aof 之后,就得执行 fsync /* redis_fsync is defined as fdatasync() for Linux in order to avoid * flushing metadata. */ latencyStartMonitor(latency); /* Let's try to get this data on the disk. To guarantee data safe when * the AOF fsync policy is 'always', we should exit if failed to fsync * AOF (see comment next to the exit(1) after write error above). */ if (redis_fsync(server.aof_fd) == -1) { serverLog(LL_WARNING,"Can't persist AOF for fsync error when the " "AOF fsync policy is 'always': %s. Exiting...", strerror(errno)); exit(1); } latencyEndMonitor(latency); latencyAddSampleIfNeeded("aof-fsync-always",latency); server.aof_fsync_offset = server.aof_current_size; server.aof_last_fsync = server.unixtime; } elseif ((server.aof_fsync == AOF_FSYNC_EVERYSEC && server.unixtime > server.aof_last_fsync)) { // 如果是 AOF_FSYNC_EVERYSEC,则每秒执行一次 // 注意:由于 server.unixtime 是一个缓存,每秒更新一次(参考 updateCachedTime 方法), // 所以只用 server.unixtime > server.aof_last_fsync,就可以保证“每秒处理一次” if (!sync_in_progress) { // 注意,此处是创建一个后台 fsync 任务,这里就和方法开始的 // sync_in_progress = aofFsyncInProgress() 检测任务呼应上了。 aof_background_fsync(server.aof_fd); server.aof_fsync_offset = server.aof_current_size; } server.aof_last_fsync = server.unixtime; } }
Active expired keys collection (it is also performed in a lazy way on lookup).
Software watchdog.
Update some statistic.
Incremental rehashing of the DBs hash tables.
Triggering BGSAVE / AOF rewrite, and handling of terminated children.
Clients timeout of different kinds.
Replication reconnection.
Many more…
1 2 3 4 5 6 7 8 9 10 11
voidinitServer(void){ ...... /* Create the timer callback, this is our way to process many background * operations incrementally, like clients timeout, eviction of unaccessed * expired keys and so forth. */ if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) { serverPanic("Can't create event loop timers."); exit(1); } ...... }
/* 当执行了 kill -9 或者 shutdown,会在这里优雅的关停服务(比如 flush aof 文件、创建 RDB file 等) */ if (server.shutdown_asap) { if (prepareForShutdown(SHUTDOWN_NOFLAGS) == C_OK) exit(0); serverLog(LL_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information"); server.shutdown_asap = 0; } ······
/* 判断是否需要 BGSAVE * Save if we reached the given amount of changes, * the given amount of seconds, and if the latest bgsave was * successful or if, in case of an error, at least * CONFIG_BGSAVE_RETRY_DELAY seconds already elapsed. */ if (server.dirty >= sp->changes && server.unixtime-server.lastsave > sp->seconds && (server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || server.lastbgsave_status == C_OK)) { serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...", sp->changes, (int)sp->seconds); rdbSaveInfo rsi, *rsiptr; rsiptr = rdbPopulateSaveInfo(&rsi); rdbSaveBackground(server.rdb_filename,rsiptr); break; } }
if (hasActiveChildProcess()) return C_ERR; if (aofCreatePipes() != C_OK) return C_ERR; if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) { char tmpfile[256];
/* Child */ redisSetProcTitle("redis-aof-rewrite"); redisSetCpuAffinity(server.aof_rewrite_cpulist); snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); if (rewriteAppendOnlyFile(tmpfile) == C_OK) { sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite"); exitFromChild(0); } else { exitFromChild(1); } } else { /* Parent */ if (childpid == -1) { serverLog(LL_WARNING, "Can't rewrite append only file in background: fork: %s", strerror(errno)); aofClosePipes(); return C_ERR; } serverLog(LL_NOTICE, "Background append only file rewriting started by pid %ld",(long) childpid); server.aof_rewrite_scheduled = 0; server.aof_rewrite_time_start = time(NULL);
/* We set appendseldb to -1 in order to force the next call to the * feedAppendOnlyFile() to issue a SELECT command, so the differences * accumulated by the parent into server.aof_rewrite_buf will start * with a SELECT statement and it will be safe to merge. */ server.aof_selected_db = -1; replicationScriptCacheFlush(); return C_OK; } return C_OK; /* unreached */ }
/* Write a sequence of commands able to fully rebuild the dataset into * "filename". Used both by REWRITEAOF and BGREWRITEAOF. * * In order to minimize the number of commands needed in the rewritten * log Redis uses variadic commands when possible, such as RPUSH, SADD * and ZADD. However at max AOF_REWRITE_ITEMS_PER_CMD items per time * are inserted using a single command. */ intrewriteAppendOnlyFile(char *filename){ rio aof; FILE *fp = NULL; char tmpfile[256]; char byte;
/* Note that we have to use a different temp name here compared to the * one used by rewriteAppendOnlyFileBackground() function. */ snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid()); fp = fopen(tmpfile,"w"); if (!fp) { serverLog(LL_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno)); return C_ERR; }
/* Set the file-based rio object to auto-fsync every 'bytes' file written. * By default this is set to zero that means no automatic file sync is * performed. * * This feature is useful in a few contexts since when we rely on OS write * buffers sometimes the OS buffers way too much, resulting in too many * disk I/O concentrated in very little time. When we fsync in an explicit * way instead the I/O pressure is more distributed across time. */ if (server.aof_rewrite_incremental_fsync) rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
/* Do an initial slow fsync here while the parent is still sending * data, in order to make the next final fsync faster. */ if (fflush(fp) == EOF) goto werr; if (fsync(fileno(fp)) == -1) goto werr;
/* 因为主进程可能会持续收到新命令,这里尝试从主进程多度一些数据 * 最多多读 1s,如果 20ms 内没有新的数据,也会跳过这部分代码. */ int nodata = 0; mstime_t start = mstime(); while(mstime()-start < 1000 && nodata < 20) { if (aeWait(server.aof_pipe_read_data_from_parent, AE_READABLE, 1) <= 0) { nodata++; continue; } nodata = 0; /* Start counting from zero, we stop on N *contiguous* timeouts. */ aofReadDiffFromParent(); }
/* 通知主进程不需要再发 diff 数据了*/ if (write(server.aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr; if (anetNonBlock(NULL,server.aof_pipe_read_ack_from_parent) != ANET_OK) goto werr;
/* ---------------------------------------------------------------------------- * AOF rewrite buffer implementation. * * The following code implement a simple buffer used in order to accumulate * changes while the background process is rewriting the AOF file. * * We only need to append, but can't just use realloc with a large block * because 'huge' reallocs are not always handled as one could expect * (via remapping of pages at OS level) but may involve copying data. * * For this reason we use a list of blocks, every block is * AOF_RW_BUF_BLOCK_SIZE bytes. * ------------------------------------------------------------------------- */
#define AOF_RW_BUF_BLOCK_SIZE (1024*1024*10) /* 10 MB per block */
voidaofRewriteBufferAppend(unsignedchar *s, unsignedlong len){ ...... /* Install a file event to send data to the rewrite child if there is * not one already. */ if (!server.aof_stop_sending_diff && aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0) { aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child, AE_WRITABLE, aofChildWriteDiffData, NULL); } }
如果 redis 运行了一段时间,这时候开启 aof,会发生什么? startAppendOnly(void) 方法:Called when the user switches from “appendonly no” to “appendonly yes” at runtime using the CONFIG command. 其中调用了 rewriteAppendOnlyFileBackground(),执行了一次 rewrite,将 redis 中的数据全量备份到了 aof 文件中。
# Save the DB to disk. # # save <seconds><changes> # # Redis will save the DB if both the given number of seconds and the given # number of write operations against the DB occurred. # # Snapshotting can be completely disabled with a single empty string argument # as in following example: # # save "" # # Unless specified otherwise, by default Redis will save the DB: # * After 3600seconds (an hour) if at least 1 key changed # * After 300seconds (5 minutes) if at least 100 keys changed # * After 60 seconds if at least 10000 keys changed # # You can set these explicitly by uncommenting the three following lines. # # save 3600 1 # save 300 100 # save 60 10000
默认情况下,redis 初始化的时候,就会执行如下命令
1 2 3
appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */ appendServerSaveParams(300,100); /* save after 5 minutes and 100 changes */ appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */
/* Save if we reached the given amount of changes, * the given amount of seconds, and if the latest bgsave was * successful or if, in case of an error, at least * CONFIG_BGSAVE_RETRY_DELAY seconds already elapsed. */ if (server.dirty >= sp->changes && server.unixtime-server.lastsave > sp->seconds && (server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || server.lastbgsave_status == C_OK)) { serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...", sp->changes, (int)sp->seconds); rdbSaveInfo rsi, *rsiptr; rsiptr = rdbPopulateSaveInfo(&rsi); rdbSaveBackground(server.rdb_filename,rsiptr); break; } }
/* Trigger an AOF rewrite if needed. */ if (server.aof_state == AOF_ON && !hasActiveChildProcess() && server.aof_rewrite_perc && server.aof_current_size > server.aof_rewrite_min_size) { }
······
/* 如果 client 发送命令执行 bgsave 的时候,有子任务(比如 AOF rewrite、bgSave)正在运行, * 则停止本次任务,返回 “Background saving scheduled” * 同时标记 rdb_bgsave_scheduled,等到 serverCron 中下面这一段代码再做补偿: * Start a scheduled BGSAVE if the corresponding flag is set. This is * useful when we are forced to postpone a BGSAVE because an AOF * rewrite is in progress. */ if (!hasActiveChildProcess() && server.rdb_bgsave_scheduled && (server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || server.lastbgsave_status == C_OK)) { rdbSaveInfo rsi, *rsiptr; rsiptr = rdbPopulateSaveInfo(&rsi); if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) server.rdb_bgsave_scheduled = 0; } ······ }
/* Save the DB on disk. Return C_ERR on error, C_OK on success. */ intrdbSave(char *filename, rdbSaveInfo *rsi){ ······ /* rio 是 redis 提供的一个 IO 模块,将 buffer、file、socket 的读写,统一抽象成下面三个方法: * read: read from stream. * write: write to stream. * tell: get the current offset. */ rioInitWithFile(&rdb,fp);
/* 定期更新一下子进程的信息。Update child info every 1 second (approximately). * in order to avoid calling mstime() on each iteration, we will * check the diff every 1024 keys */ if ((key_count++ & 1023) == 0) { longlong now = mstime(); if (now - info_updated_time >= 1000) { sendChildInfo(CHILD_INFO_TYPE_CURRENT_INFO, key_count, pname); info_updated_time = now; } } } dictReleaseIterator(di); di = NULL; /* So that we don't release it again on error. */ }
······
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr;
/* EOF opcode */ if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
/* CRC64 checksum. It will be zero if checksum computation is disabled, the * loading code skips the check in this case. */ cksum = rdb->cksum; memrev64ifbe(&cksum); if (rioWrite(rdb,&cksum,8) == 0) goto werr; return C_OK;
werr: if (error) *error = errno; if (di) dictReleaseIterator(di); return C_ERR; }
/* Save a key-value pair, with expire time, type, key, value. * On error -1 is returned. * On success if the key was actually saved 1 is returned. */ intrdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, longlong expiretime){ int savelru = server.maxmemory_policy & MAXMEMORY_FLAG_LRU; int savelfu = server.maxmemory_policy & MAXMEMORY_FLAG_LFU;
/* Save the expire time */ if (expiretime != -1) { if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return-1; if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return-1; }
/* Save the LRU info. */ if (savelru) { uint64_t idletime = estimateObjectIdleTime(val); idletime /= 1000; /* Using seconds is enough and requires less space.*/ if (rdbSaveType(rdb,RDB_OPCODE_IDLE) == -1) return-1; if (rdbSaveLen(rdb,idletime) == -1) return-1; }
/* Save the LFU info. */ if (savelfu) { uint8_t buf[1]; buf[0] = LFUDecrAndReturn(val); /* We can encode this in exactly two bytes: the opcode and an 8 * bit counter, since the frequency is logarithmic with a 0-255 range. * Note that we do not store the halving time because to reset it * a single time when loading does not affect the frequency much. */ if (rdbSaveType(rdb,RDB_OPCODE_FREQ) == -1) return-1; if (rdbWriteRaw(rdb,buf,1) == -1) return-1; }
/* 保存:类型、key、value */ if (rdbSaveObjectType(rdb,val) == -1) return-1; if (rdbSaveStringObject(rdb,key) == -1) return-1; if (rdbSaveObject(rdb,val,key) == -1) return-1; ....... }