Redis源碼分析
基於Redis-5.0.4版本,進行基礎的源碼分析,主要就是分析一些平常使用過程中的內容。僅作爲相關內容的學習記錄,有關Redis源碼學習閱讀比較廣泛的便是《Redis設計與實現》一書,瀏覽學習該書之後,覺得相關內容還是覺得抽象的高度比較高,故對照着代碼再閱讀學習一下。
Redis的數據結構
- 二進制安全的字符串,二進制安全的意思大家可以自行查閱定義,在redis中的字符串安全,主要就是在C語言中的字符串的長度判斷與溢出的判斷都是通過單獨維護一個len字段來進行字符串的相關長度檢查,並沒有直接依靠C語言的字符串數組來進行檢查。
- 列表,根據插入順序的字符串列表,主要就是通過鏈表來實現。
- 集合,主要就是不重複的不排序的字符串集合。
- 有序集合,與Sets類似,但是每個字符串元素都與一個稱爲score的浮點值相關聯。 元素總是按它們的分數排序,因此與Sets不同,可以檢索一系列元素(例如,您可能會問:給我前10名或後10名)。
- 哈希字典,是由與值相關聯的字段組成的map字段和值都是字符串,這與Ruby或Python哈希非常相似。
- 位數組,可以使用特殊命令像位數組一樣處理字符串值:您可以設置和清除單個位,計數所有設置爲1的位,找到第一個設置或未設置的位,依此類推。
- HyperLogLogs,這是一個概率數據結構,用於估計集合的基數。
- 流:提供抽象日誌數據類型的類地圖項的僅追加集合。
這基本上就是官網上介紹的八種數據結構,其中流這種數據結構是版本5開始添加進來的主要就是完善對日誌流的相關操作的補充。
Redis的工作模式與初始化流程
Redis服務端的工作模式,主要都是Reactor模式,該模式主要就是被稱爲文件事件處理方式,主線程通過使用IO多路複用技術來同事監聽多個套接字,並根據套接字目前執行的任務來爲套接字處理不同的事件處理器,當被監聽的套接字準備好接受連接(accept)、讀取(read)、寫入(write)或者關閉(close)時都會操作相對應的文件事件,這樣就達到了單線程來處理大量客戶端請求的情況,Redis服務端選擇了單線程的Reactor模式,即所有的業務的處理都在一個線程中來進行,單線程的響應模式也保證了Redis執行某些命令所需要的原子操作,但是Redis服務器本身中也會出現會需要一些其他線程或者進程來做的事情,但是這些都是爲了支持一些服務器本身的如備份等其他功能操作。
接下來就通過Redis服務端的初始化過程簡單的概述一下Redis的工作模式。
server的main函數
int main(int argc, char **argv) {
struct timeval tv;
int j;
#ifdef REDIS_TEST // 是否是測試
if (argc == 3 && !strcasecmp(argv[1], "test")) {
if (!strcasecmp(argv[2], "ziplist")) {
return ziplistTest(argc, argv);
} else if (!strcasecmp(argv[2], "quicklist")) {
quicklistTest(argc, argv);
} else if (!strcasecmp(argv[2], "intset")) {
return intsetTest(argc, argv);
} else if (!strcasecmp(argv[2], "zipmap")) {
return zipmapTest(argc, argv);
} else if (!strcasecmp(argv[2], "sha1test")) {
return sha1Test(argc, argv);
} else if (!strcasecmp(argv[2], "util")) {
return utilTest(argc, argv);
} else if (!strcasecmp(argv[2], "sds")) {
return sdsTest(argc, argv);
} else if (!strcasecmp(argv[2], "endianconv")) {
return endianconvTest(argc, argv);
} else if (!strcasecmp(argv[2], "crc64")) {
return crc64Test(argc, argv);
} else if (!strcasecmp(argv[2], "zmalloc")) {
return zmalloc_test(argc, argv);
}
return -1; /* test not found */
}
#endif
/* We need to initialize our libraries, and the server configuration. */
#ifdef INIT_SETPROCTITLE_REPLACEMENT
spt_init(argc, argv);
#endif
setlocale(LC_COLLATE,""); // 設置地域信息
tzset(); /* Populates 'timezone' global. */
zmalloc_set_oom_handler(redisOutOfMemoryHandler); // 設置超過內存處理函數
srand(time(NULL)^getpid()); // 隨機數產生器的初始值
gettimeofday(&tv,NULL); // 獲取時間
char hashseed[16];
getRandomHexChars(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed((uint8_t*)hashseed); // 設置hash函數的值
server.sentinel_mode = checkForSentinelMode(argc,argv); // 檢查是否啓動的是哨兵模式
initServerConfig(); // 初始化服務器配置
moduleInitModulesSystem(); // 加載擴展的模塊
/* Store the executable path and arguments in a safe place in order
* to be able to restart the server later. */
server.executable = getAbsolutePath(argv[0]); // 保存可執行文件
server.exec_argv = zmalloc(sizeof(char*)*(argc+1)); // 申請內存保存輸入的參數
server.exec_argv[argc] = NULL;
for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]); // 保存輸入的參數,
/* We need to init sentinel right now as parsing the configuration file
* in sentinel mode will have the effect of populating the sentinel
* data structures with master nodes to monitor. */
if (server.sentinel_mode) { // 如果是哨兵模式則初始化哨兵模式配置
initSentinelConfig();
initSentinel();
}
/* Check if we need to start in redis-check-rdb/aof mode. We just execute
* the program main. However the program is part of the Redis executable
* so that we can easily execute an RDB check on loading errors. */
if (strstr(argv[0],"redis-check-rdb") != NULL) // 檢查是否包含了redis-check-rdb參數 如果有則檢查rdb文件是否正確
redis_check_rdb_main(argc,argv,NULL);
else if (strstr(argv[0],"redis-check-aof") != NULL) // 檢查是否包括了aof文件的檢查 如果有則檢查aof文件
redis_check_aof_main(argc,argv);
if (argc >= 2) { // 如果輸入參數多餘等於兩個 則檢查是否包含如下的輸入參數
j = 1; /* First option to parse in argv[] */
sds options = sdsempty();
char *configfile = NULL;
/* Handle special options --help and --version */
if (strcmp(argv[1], "-v") == 0 ||
strcmp(argv[1], "--version") == 0) version();
if (strcmp(argv[1], "--help") == 0 ||
strcmp(argv[1], "-h") == 0) usage();
if (strcmp(argv[1], "--test-memory") == 0) {
if (argc == 3) {
memtest(atoi(argv[2]),50);
exit(0);
} else {
fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n");
fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n");
exit(1);
}
}
/* First argument is the config file name? */
if (argv[j][0] != '-' || argv[j][1] != '-') {
configfile = argv[j];
server.configfile = getAbsolutePath(configfile);
/* Replace the config file in server.exec_argv with
* its absolute path. */
zfree(server.exec_argv[j]);
server.exec_argv[j] = zstrdup(server.configfile);
j++;
}
/* All the other options are parsed and conceptually appended to the
* configuration file. For instance --port 6380 will generate the
* string "port 6380\n" to be parsed after the actual file name
* is parsed, if any. */
while(j != argc) {
if (argv[j][0] == '-' && argv[j][1] == '-') {
/* Option name */
if (!strcmp(argv[j], "--check-rdb")) {
/* Argument has no options, need to skip for parsing. */
j++;
continue;
}
if (sdslen(options)) options = sdscat(options,"\n");
options = sdscat(options,argv[j]+2);
options = sdscat(options," ");
} else {
/* Option argument */
options = sdscatrepr(options,argv[j],strlen(argv[j]));
options = sdscat(options," ");
}
j++;
}
if (server.sentinel_mode && configfile && *configfile == '-') {
serverLog(LL_WARNING,
"Sentinel config from STDIN not allowed.");
serverLog(LL_WARNING,
"Sentinel needs config file on disk to save state. Exiting...");
exit(1);
}
resetServerSaveParams();
loadServerConfig(configfile,options);
sdsfree(options);
}
serverLog(LL_WARNING, "oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo");
serverLog(LL_WARNING,
"Redis version=%s, bits=%d, commit=%s, modified=%d, pid=%d, just started",
REDIS_VERSION,
(sizeof(long) == 8) ? 64 : 32,
redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0,
(int)getpid()); // 輸出運行的基本信息
if (argc == 1) {
serverLog(LL_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");
} else {
serverLog(LL_WARNING, "Configuration loaded"); // 檢查是否輸入指定的輸入參數
}
server.supervised = redisIsSupervised(server.supervised_mode); // 是否是supervised模式
int background = server.daemonize && !server.supervised; // 是否是守護進程模式
if (background) daemonize(); // 如果是守護進程模式則啓動守護進程模式
initServer(); // 初始化服務端
if (background || server.pidfile) createPidFile(); // 是否是後臺啓動 並且是否配置了Pid文件路徑 如果有則創建Pid文件
redisSetProcTitle(argv[0]);
redisAsciiArt();
checkTcpBacklogSettings(); // 檢查tcp相關配置
if (!server.sentinel_mode) { // 是否是哨兵模式
/* Things not needed when running in Sentinel mode. */
serverLog(LL_WARNING,"Server initialized");
#ifdef __linux__
linuxMemoryWarnings();
#endif
moduleLoadFromQueue();
loadDataFromDisk();
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
serverLog(LL_WARNING,
"You can't have keys in a DB different than DB 0 when in "
"Cluster mode. Exiting.");
exit(1);
}
}
if (server.ipfd_count > 0)
serverLog(LL_NOTICE,"Ready to accept connections");
if (server.sofd > 0)
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
} else {
sentinelIsRunning();
}
/* Warning the user about suspicious maxmemory setting. */
if (server.maxmemory > 0 && server.maxmemory < 1024*1024) { // 檢查最大內容的大小 是否打印警告信息
serverLog(LL_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);
}
aeSetBeforeSleepProc(server.el,beforeSleep); // 設置beforeSleep的回調函數
aeSetAfterSleepProc(server.el,afterSleep); // 設置afterSleep的回調函數
aeMain(server.el); // 啓動事件循環
aeDeleteEventLoop(server.el); // 事件循環結束刪除配置文件中的事件
return 0;
}
從main函數的執行流程可知,主要就是進行了相關的輸入參數的檢查與服務器端的啓動模式之間的檢查,根據不同的輸入參數啓動不同的方式來工作,在整個流程中的啓動流程中,比較主要的過程就是initServer的初始化流程與aeMain函數的執行過程。
initServer函數的概述
void initServer(void) {
int j;
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
setupSignalHandlers(); // 註冊信號處理函數
if (server.syslog_enabled) { // 是否打開syslog
openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
server.syslog_facility);
}
server.hz = server.config_hz;
server.pid = getpid(); // 獲取當前運行的進程號
server.current_client = NULL; // 記錄當前執行命令的客戶端
server.clients = listCreate(); // 當前所有連接的客戶端 通過一個列表來保存
server.clients_index = raxNew();
server.clients_to_close = listCreate(); // 獲取一個需要關閉的clients列表
server.slaves = listCreate(); // 生成一個從節點的列表
server.monitors = listCreate(); // 生成一個監視器的列表
server.clients_pending_write = listCreate();
server.slaveseldb = -1; /* Force to emit the first SELECT command. */
server.unblocked_clients = listCreate(); // 生成一個不阻塞的客戶端列表
server.ready_keys = listCreate();
server.clients_waiting_acks = listCreate();
server.get_ack_from_slaves = 0;
server.clients_paused = 0;
server.system_memory_size = zmalloc_get_memory_size(); // 系統的內存大小
createSharedObjects(); // 創建objects
adjustOpenFilesLimit();
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR); // 創建事件循環
if (server.el == NULL) {
serverLog(LL_WARNING,
"Failed creating the event loop. Error message: '%s'",
strerror(errno));
exit(1);
}
server.db = zmalloc(sizeof(redisDb)*server.dbnum); // 初始化數據庫數量
/* Open the TCP listening socket for the user commands. */
if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR) // 監聽端口
exit(1);
/* Open the listening Unix domain socket. */
if (server.unixsocket != NULL) { // 如果unix套接字配置不爲空則創建原始套接字接口
unlink(server.unixsocket); /* don't care if this fails */
server.sofd = anetUnixServer(server.neterr,server.unixsocket,
server.unixsocketperm, server.tcp_backlog);
if (server.sofd == ANET_ERR) {
serverLog(LL_WARNING, "Opening Unix socket: %s", server.neterr);
exit(1);
}
anetNonBlock(NULL,server.sofd);
}
/* Abort if there are no listening sockets at all. */
if (server.ipfd_count == 0 && server.sofd < 0) {
serverLog(LL_WARNING, "Configured to not listen anywhere, exiting.");
exit(1);
}
/* Create the Redis databases, and initialize other internal state. */ // 初始化每個數據庫
for (j = 0; j < server.dbnum; j++) {
server.db[j].dict = dictCreate(&dbDictType,NULL); // 數據庫的字典
server.db[j].expires = dictCreate(&keyptrDictType,NULL); // 過期字典
server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL); // 阻塞的key列表
server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL); // 觀察的key字典
server.db[j].id = j;
server.db[j].avg_ttl = 0;
server.db[j].defrag_later = listCreate();
}
evictionPoolAlloc(); /* Initialize the LRU keys pool. */ // 註冊LRU緩衝池
server.pubsub_channels = dictCreate(&keylistDictType,NULL); // 初始化發佈訂閱的字典
server.pubsub_patterns = listCreate(); // 發佈訂閱的列表
listSetFreeMethod(server.pubsub_patterns,freePubsubPattern); // 註冊相關的釋放與匹配函數
listSetMatchMethod(server.pubsub_patterns,listMatchPubsubPattern);
server.cronloops = 0;
server.rdb_child_pid = -1; // rdb子進程
server.aof_child_pid = -1; // aof子進程
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
server.rdb_bgsave_scheduled = 0;
server.child_info_pipe[0] = -1; // 通信管道
server.child_info_pipe[1] = -1;
server.child_info_data.magic = 0;
aofRewriteBufferReset();
server.aof_buf = sdsempty(); // aof緩存
server.lastsave = time(NULL); /* At startup we consider the DB saved. */
server.lastbgsave_try = 0; /* At startup we never tried to BGSAVE. */
server.rdb_save_time_last = -1;
server.rdb_save_time_start = -1;
server.dirty = 0;
resetServerStats();
/* A few stats we don't want to reset: server startup time, and peak mem. */
server.stat_starttime = time(NULL); // 開始時間
server.stat_peak_memory = 0;
server.stat_rdb_cow_bytes = 0;
server.stat_aof_cow_bytes = 0;
server.cron_malloc_stats.zmalloc_used = 0;
server.cron_malloc_stats.process_rss = 0;
server.cron_malloc_stats.allocator_allocated = 0;
server.cron_malloc_stats.allocator_active = 0;
server.cron_malloc_stats.allocator_resident = 0;
server.lastbgsave_status = C_OK;
server.aof_last_write_status = C_OK;
server.aof_last_write_errno = 0;
server.repl_good_slaves_count = 0;
/* 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);
}
/* Create an event handler for accepting new connections in TCP and Unix
* domain sockets. */
for (j = 0; j < server.ipfd_count; j++) {
if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR) // 創建事件監聽的接受事件,回調函數爲acceptTcpHandler
{
serverPanic(
"Unrecoverable error creating server.ipfd file event.");
}
}
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE, // 創建接受unix原始套接字的接受事件
acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
/* Register a readable event for the pipe used to awake the event loop
* when a blocked client in a module needs attention. */
if (aeCreateFileEvent(server.el, server.module_blocked_pipe[0], AE_READABLE,
moduleBlockedClientPipeReadable,NULL) == AE_ERR) { // 創建模塊的管道溝通的回調事件
serverPanic(
"Error registering the readable event for the module "
"blocked clients subsystem.");
}
/* Open the AOF file if needed. */
if (server.aof_state == AOF_ON) { // 是否打開aof文件
server.aof_fd = open(server.aof_filename,
O_WRONLY|O_APPEND|O_CREAT,0644);
if (server.aof_fd == -1) {
serverLog(LL_WARNING, "Can't open the append-only file: %s",
strerror(errno));
exit(1);
}
}
/* 32 bit instances are limited to 4GB of address space, so if there is
* no explicit limit in the user provided configuration we set a limit
* at 3 GB using maxmemory with 'noeviction' policy'. This avoids
* useless crashes of the Redis instance for out of memory. */
if (server.arch_bits == 32 && server.maxmemory == 0) {
serverLog(LL_WARNING,"Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now.");
server.maxmemory = 3072LL*(1024*1024); /* 3 GB */
server.maxmemory_policy = MAXMEMORY_NO_EVICTION;
}
if (server.cluster_enabled) clusterInit(); // 是否集羣模式
replicationScriptCacheInit();
scriptingInit(1);
slowlogInit(); // 慢日誌初始化
latencyMonitorInit(); // 監控初始化
bioInit();
server.initial_memory_usage = zmalloc_used_memory(); // 可以使用的內存大小
}
該函數主要就是根據傳入的配置文件的參數,初始化數據庫大小,創建時間事件,並且創建客戶端連接請求的事件,接着再初始化對應的一些腳本監控等初始化函數即完成了初始化過程。
aeMain函數
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}
}
該函數主要就是通過一個while循環,每次都先檢查一下beforesleep是否設置了回調函數,如果設置了則調用該函數執行該函數主要做了一些集羣相關的操作和aof相關的檢查等操作後續可以詳細分析一下該函數,執行完成之後就執行aeProcessEvents。
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int processed = 0, numevents;
/* Nothing to do? return ASAP */
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; // 如果沒有可以處理的事件則返回
/* Note that we want call select() even if there are no
* file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
if (eventLoop->maxfd != -1 ||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j;
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp;
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
shortest = aeSearchNearestTimer(eventLoop); // 查找最近的時間事件
if (shortest) {
long now_sec, now_ms;
aeGetTime(&now_sec, &now_ms);
tvp = &tv;
/* How many milliseconds we need to wait for the next
* time event to fire? */
long long ms =
(shortest->when_sec - now_sec)*1000 +
shortest->when_ms - now_ms; // 檢查還需要多久可以執行該事件 或者時間已經到了可以執行該事件
if (ms > 0) {
tvp->tv_sec = ms/1000;
tvp->tv_usec = (ms % 1000)*1000;
} else {
tvp->tv_sec = 0;
tvp->tv_usec = 0;
}
} else {
/* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to set the timeout
* to zero */
if (flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else {
/* Otherwise we can block */
tvp = NULL; /* wait forever */
}
}
/* Call the multiplexing API, will return only on timeout or when
* some event fires. */
numevents = aeApiPoll(eventLoop, tvp); // 事件處理函數 即 select epoll等
/* After sleep callback. */
if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
eventLoop->aftersleep(eventLoop); // 是否需要執行aftersleep的回調函數
for (j = 0; j < numevents; j++) { // 獲取有多少個已經響應的事件
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
int mask = eventLoop->fired[j].mask;
int fd = eventLoop->fired[j].fd;
int fired = 0; /* Number of events fired for current fd. */
/* Normally we execute the readable event first, and the writable
* event laster. This is useful as sometimes we may be able
* to serve the reply of a query immediately after processing the
* query.
*
* However if AE_BARRIER is set in the mask, our application is
* asking us to do the reverse: never fire the writable event
* after the readable. In such a case, we invert the calls.
* This is useful when, for instance, we want to do things
* in the beforeSleep() hook, like fsynching a file to disk,
* before replying to a client. */
int invert = fe->mask & AE_BARRIER;
/* Note the "fe->mask & mask & ..." code: maybe an already
* processed event removed an element that fired and we still
* didn't processed, so we check if the event is still valid.
*
* Fire the readable event if the call sequence is not
* inverted. */
if (!invert && fe->mask & mask & AE_READABLE) { // 是否是可讀事件 如果是可讀事件則調用該事件的回調方法並傳入當前的數據
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
/* Fire the writable event. */
if (fe->mask & mask & AE_WRITABLE) { // 如果是科協時間 則處理可寫事件的回調方法
if (!fired || fe->wfileProc != fe->rfileProc) {
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
/* If we have to invert the call, fire the readable event now
* after the writable one. */
if (invert && fe->mask & mask & AE_READABLE) {
if (!fired || fe->wfileProc != fe->rfileProc) {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
processed++;
}
}
/* Check time events */
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop); // 如果當前有時間事件需要執行則調用時間事件執行
return processed; /* return the number of processed file/time events */
}
主要就是分層級執行了不同的任務,首先先檢查是否有時間到期的時間事件需要執行,然後再檢查是否有響應的任務事件需要回調執行,如果有響應任務需要執行則先執行響應任務,然後再執行時間事件。
/* Process time events */
static int processTimeEvents(aeEventLoop *eventLoop) {
int processed = 0;
aeTimeEvent *te;
long long maxId;
time_t now = time(NULL); // 獲取當前時間
/* If the system clock is moved to the future, and then set back to the
* right value, time events may be delayed in a random way. Often this
* means that scheduled operations will not be performed soon enough.
*
* Here we try to detect system clock skews, and force all the time
* events to be processed ASAP when this happens: the idea is that
* processing events earlier is less dangerous than delaying them
* indefinitely, and practice suggests it is. */
if (now < eventLoop->lastTime) { // 檢查當前時間是否小於最後調用的時間
te = eventLoop->timeEventHead;
while(te) { // 一次循環將when_sec置空
te->when_sec = 0;
te = te->next;
}
}
eventLoop->lastTime = now; // 時間置爲當前時間
te = eventLoop->timeEventHead; // 獲取事件的頭部
maxId = eventLoop->timeEventNextId-1;
while(te) {
long now_sec, now_ms;
long long id;
/* Remove events scheduled for deletion. */
if (te->id == AE_DELETED_EVENT_ID) { // 是否執行完成之後移除該任務
aeTimeEvent *next = te->next; // 移除該任務
if (te->prev)
te->prev->next = te->next;
else
eventLoop->timeEventHead = te->next;
if (te->next)
te->next->prev = te->prev;
if (te->finalizerProc)
te->finalizerProc(eventLoop, te->clientData); // 執行該任務
zfree(te);
te = next;
continue;
}
/* Make sure we don't process time events created by time events in
* this iteration. Note that this check is currently useless: we always
* add new timers on the head, however if we change the implementation
* detail, this check may be useful again: we keep it here for future
* defense. */
if (te->id > maxId) {
te = te->next;
continue;
}
aeGetTime(&now_sec, &now_ms);
if (now_sec > te->when_sec ||
(now_sec == te->when_sec && now_ms >= te->when_ms)) // 更精確的時間對比
{
int retval;
id = te->id;
retval = te->timeProc(eventLoop, id, te->clientData); // 執行任務 根據任務的執行返回來判斷是否繼續加入時間循環
processed++;
if (retval != AE_NOMORE) { // 繼續延長該任務時間來繼續執行
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
} else {
te->id = AE_DELETED_EVENT_ID; // 設置刪除任務id
}
}
te = te->next; // 下一個
}
return processed;
}
處理時間事件,也分爲定時執行即執行完成之後繼續延長指定時間之後還會再執行,也分爲執行當次任務執行就不再繼續執行,時間事件主要是通過時間的比較判斷來進行並進行事件的回調處理。
總結
本文主要就是概述了Redis相關的基礎的數據結構,5.0.4版本的Redis的啓動流程除了增加了許多新功能外,基本的啓動流程與以前整理的redis源碼分析(beta版本)的啓動流程基本一致,都是單線程的事件驅動模式,事件中包括了連接的文件描述符事件,也包括了時間事件的處理。後續會再深入探討5.0.4版本的Redis的其他的功能實現。由於本人才疏學淺,如有錯誤請批評指正。