redis事件循环处理框架

redis服务器启动以后,先进行初始化配置, 然后进入主事件循环中监听事件到来,并进行处理.
函数aeProcessEvents进行事件监听和事件处理,其实现为(ae.c):

162 /* Process every pending time event, then every pending file event
163  * (that may be registered by time event callbacks just processed).
164  * Without special flags the function sleeps until some file event
165  * fires, or when the next time event occurrs (if any).
166  *
167  * If flags is 0, the function does nothing and returns.
168  * if flags has AE_ALL_EVENTS set, all the kind of events are processed.
169  * if flags has AE_FILE_EVENTS set, file events are processed.
170  * if flags has AE_TIME_EVENTS set, time events are processed.
171  * if flags has AE_DONT_WAIT set the function returns ASAP until all
172  * the events that's possible to process without to wait are processed.
173  *
174  * The function returns the number of events processed. */
175 int aeProcessEvents(aeEventLoop *eventLoop, int flags)
176 {
177     int maxfd = 0, numfd = 0, processed = 0;
178     fd_set rfds, wfds, efds;
179     aeFileEvent *fe = eventLoop->fileEventHead;
180     aeTimeEvent *te;
181     long long maxId;
182     AE_NOTUSED(flags);
183 
184     /* Nothing to do? return ASAP */
185     if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
186 
187     FD_ZERO(&rfds);
188     FD_ZERO(&wfds);
189     FD_ZERO(&efds);
190 
191     /* Check file events */
192     if (flags & AE_FILE_EVENTS) {
193         while (fe != NULL) {
194             if (fe->mask & AE_READABLE) FD_SET(fe->fd, &rfds);
195             if (fe->mask & AE_WRITABLE) FD_SET(fe->fd, &wfds);
196             if (fe->mask & AE_EXCEPTION) FD_SET(fe->fd, &efds);
197             if (maxfd < fe->fd) maxfd = fe->fd;
198             numfd++;
199             fe = fe->next;
200         }
201     }
202     /* Note that we want call select() even if there are no
203      * file events to process as long as we want to process time
204      * events, in order to sleep until the next time event is ready
205      * to fire. */
206     if (numfd || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
207         int retval;
208         aeTimeEvent *shortest = NULL;
209         struct timeval tv, *tvp;
210 
211         if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
212             shortest = aeSearchNearestTimer(eventLoop);
213         if (shortest) {
214             long now_sec, now_ms;
215 
216             /* Calculate the time missing for the nearest
217              * timer to fire. */
218             aeGetTime(&now_sec, &now_ms);
219             tvp = &tv;
220             tvp->tv_sec = shortest->when_sec - now_sec;
221             if (shortest->when_ms < now_ms) {
222                 tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
223                 tvp->tv_sec --;
224             } else {
225                 tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
226             }
227         } else {
228             /* If we have to check for events but need to return
229              * ASAP because of AE_DONT_WAIT we need to se the timeout
230              * to zero */
231             if (flags & AE_DONT_WAIT) {
232                 tv.tv_sec = tv.tv_usec = 0;
233                 tvp = &tv;
234             } else {
235                 /* Otherwise we can block */
236                 tvp = NULL; /* wait forever */
237             }
238         }
239 
240         retval = select(maxfd+1, &rfds, &wfds, &efds, tvp);
241         if (retval > 0) {
242             fe = eventLoop->fileEventHead;
243             while(fe != NULL) {
244                 int fd = (int) fe->fd;
245 
246                 if ((fe->mask & AE_READABLE && FD_ISSET(fd, &rfds)) ||
247                     (fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds)) ||
248                     (fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds)))
249                 {
250                     int mask = 0;
251 
252                     if (fe->mask & AE_READABLE && FD_ISSET(fd, &rfds))
253                         mask |= AE_READABLE;
254                     if (fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds))
255                         mask |= AE_WRITABLE;
256                     if (fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds))
257                         mask |= AE_EXCEPTION;
258                     fe->fileProc(eventLoop, fe->fd, fe->clientData, mask);
259                     processed++;
260                     /* After an event is processed our file event list
261                      * may no longer be the same, so what we do
262                      * is to clear the bit for this file descriptor and
263                      * restart again from the head. */
264                     fe = eventLoop->fileEventHead;
265                     FD_CLR(fd, &rfds);
266                     FD_CLR(fd, &wfds);
267                     FD_CLR(fd, &efds);
268                 } else {
269                     fe = fe->next;
270                 }
271             }
272         }
273     }
274     /* Check time events */
275     if (flags & AE_TIME_EVENTS) {
276         te = eventLoop->timeEventHead;
277         maxId = eventLoop->timeEventNextId-1;
278         while(te) {
279             long now_sec, now_ms;
280             long long id;
281 
282             if (te->id > maxId) {
283                 te = te->next;
284                 continue;
285             }
286             aeGetTime(&now_sec, &now_ms);
287             if (now_sec > te->when_sec ||
288                 (now_sec == te->when_sec && now_ms >= te->when_ms))
289             {
290                 int retval;
291 
292                 id = te->id;
293                 retval = te->timeProc(eventLoop, id, te->clientData);
294                 /* After an event is processed our time event list may
295                  * no longer be the same, so we restart from head.
296                  * Still we make sure to don't process events registered
297                  * by event handlers itself in order to don't loop forever.
298                  * To do so we saved the max ID we want to handle. */
299                 if (retval != AE_NOMORE) {
300                     aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
301                 } else {
302                     aeDeleteTimeEvent(eventLoop, id);
303                 }
304                 te = eventLoop->timeEventHead;
305             } else {
306                 te = te->next;
307             }
308         }
309     }
310     return processed; /* return the number of processed file/time events */
311 }

首先Line185检查要处理的事件类型, 如果flags参数没有设置定时事件和文件事件, 该函数直接返回. 该函数被调用的时候,传入flags的实参值为AE_ALL_EVENTS,, 所以定时事件和文件事件都需要处理.该函数是在单线程里面使用异步IO的方式(使用select系统调用)处理触发的事件, 接下来初始化三个文件描述符集.Line191:201获得有效的文件事件对象,根据其mask字段的值,决定是否监听指定的触发类型.对于redis刚启动状态下, 只有初始化创建的一个文件事件对象, 其maskAE_READABLE, 该对象用来监听客户端连接. 注意的是, 这个对象将永远有效,即不会被删除, 且其mask字段不会改变, 一直是AE_READABLE.对于有效的文件事件, 更新局部变量maxfd, 以满足select系统调用的规范.Line206的条件判断肯定会满足, 因为至少存在一个文件事件等待客户端连接, 及numfd用永远大于0.Line211:238获得最先超时的定时事件对象,并通过和当前时间进行比较产生差值(局部变量tvp), 这个差值就是select系统调用正确的超时时间.Line240进行select系统调用, 如果没有超时,并且没有文件事件被出发,该调用将阻塞,当其返回时, 如果有文件事件被触发,返回值大于0, 否则如果是超时返回,其返回值为0, 产生错误将返回-1.Line241:273是文件事件被触发的处理逻辑,通过遍历文件事件对象链表,检查该对象是否被触发,如果被触发,记录其触发类型,保存在局部变量mask中.然后调用该文件事件对象的fileProc指针进行处理, fileProc处理完毕以后, 有可能该文件事件对象从链表中被删除,比如客户端调用了quit redis命令终止了连接, 或者redis向客户端返回了响应命令.Line271更新局部变量fe重新指向文件事件对象链表头,以免该指针指向一个被删除的对象, 同时也是重新遍历该链表进行正确赋值.Line272:274将该触发的文件事件对象描述符从其监听集合中清除, 以免这里造成死循环.如果Line246:249的条件不满足,说明该文件事件对象没有被触发或者被触发但是已经调用其fileProc处理过了.这时Line269检查链表中下一个文件事件对象.

对于初始启动的redis服务器, 只有新的客户端连接会触发文件事件对象,该对象的fileProc对应的处理函数为acceptHandler(见redis启动初始化过程说明),该函数的实现为(redis.c):

971 static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
972     int cport, cfd;
973     char cip[128];
974     REDIS_NOTUSED(el);
975     REDIS_NOTUSED(mask);
976     REDIS_NOTUSED(privdata);
977 
978     cfd = anetAccept(server.neterr, fd, cip, &cport);
979     if (cfd == AE_ERR) {
980         redisLog(REDIS_DEBUG,"Accepting client connection: %s", server.neterr);
981         return;
982     }
983     redisLog(REDIS_DEBUG,"Accepted %s:%d", cip, cport);
984     if (createClient(cfd) == REDIS_ERR) {
985         redisLog(REDIS_WARNING,"Error allocating resoures for the client");
986         close(cfd); /* May be already closed, just ingore errors */
987         return;
988     }
989 }

Line978调用anetAccept返回新连接的客户端的socket文件描述符,这时候的accept系统调用不会阻塞,因为是新客户端的连接触发了select系统调用.Line984调用createClient为该客户端创建对应的redisClient类型的对象.

函数createClient的实现为(redis.c):

933 static int createClient(int fd) {
934     redisClient *c = malloc(sizeof(*c));
935 
936     anetNonBlock(NULL,fd);
937     anetTcpNoDelay(NULL,fd);
938     if (!c) return REDIS_ERR;
939     selectDb(c,0);
940     c->fd = fd;
941     c->querybuf = sdsempty();
942     c->argc = 0;
943     c->bulklen = -1;
944     c->sentlen = 0;
945     c->lastinteraction = time(NULL);
946     if ((c->reply = listCreate()) == NULL) oom("listCreate");
947     listSetFreeMethod(c->reply,decrRefCount);
948     if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
949         readQueryFromClient, c, NULL) == AE_ERR) {
950         freeClient(c);
951         return REDIS_ERR;
952     }
953     if (!listAddNodeTail(server.clients,c)) oom("listAddNodeTail");
954     return REDIS_OK;
955 }

首先看一下redisClient结构,其标识一个客户端,其定义为(redis.c):

69 typedef struct redisClient {
70     int fd;
71     dict *dict;
72     sds querybuf;
73     sds argv[REDIS_MAX_ARGS];
74     int argc;
75     int bulklen;    /* bulk read len. -1 if not in bulk read mode */
76     list *reply;
77     int sentlen;
78     time_t lastinteraction; /* time of the last interaction, used for timeout */
79 } redisClient;

字段fd是客户端的socket描述符,字段dict指向全局对象server中的dict对象字段,server默认有16个dict对象.新的客户端连接到来时,会选择server的第0个dict对象.dict类型是描述内存中的存储数据的结构.字段querybuf用来存储客户端协议命令及其参数,而字段argcargv代表解析后的协议命令的参数个数和具体的参数值.类型sds是一个char *类型的指针,用来和类型sdshdr配合使用,以实现动态string类型对象.字段bulklen标识bulk类型协议命令中value的字节长度,字段replylist类型的对象,记录redis响应命令,需要注意的是redis服务器发送给客户端的响应的各个组成部分可能是多个网络传输,所以这个是一个链表记录响应的各个组成部分.自段sentlen是再发送响应时,实际向socket写入的数据,用于根据响应命令数据是否发送完成.字段lastinteraction标识该客户端和redis服务器最近的交互时间戳, 如果客户端长时间没有和redis服务器进行交互, redis服务器有可能会中断同该客户端的连接.

回到函数createClient中,Line941为字段querybuf创建一个空的sds对象. 空的sds由函数sdsempty创建, 非空的sds由函数sdsnew创建.为字段reply创建一个list类型的对象,该对象初始状态下, 链表节点为空.Line948:952调用函数aeCreateFileEvent为该客户端创建一个文件事件对象,插入到全局的文件事件管理链表中.redis协议命令有客户端发送到服务器,服务器向客户端发送响应结果, 因此设置该文件事件对象的触发类型为AE_READABLE, 并且其触发后的处理函数为readQueryFromClient.Line953将该客户端对象插入到全局链表中.

Line240:273中的文件事件处理完毕后, Line274:309将处理定时事件处理逻辑.和文件事件处理类似,定时事件也是循环检查全局的定时时间对象链表,Line286:289判断该定时事件对象是否超时,如果已经超时,则处理该定时事件对象,如果未超时,检查全局链表中的下一个定时事件对象.Line293为调用该定时事件对象的处理函数,函数指针timeProc指向的是serverCron(见redis初始化处理部分).该函数的实现为(redis.c);

466 int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
467     int j, size, used, loops = server.cronloops++;
468     REDIS_NOTUSED(eventLoop);
469     REDIS_NOTUSED(id);
470     REDIS_NOTUSED(clientData);
471 
472     /* If the percentage of used slots in the HT reaches REDIS_HT_MINFILL
473      * we resize the hash table to save memory */
474     for (j = 0; j < server.dbnum; j++) {
475         size = dictGetHashTableSize(server.dict[j]);
476         used = dictGetHashTableUsed(server.dict[j]);
477         if (!(loops % 5) && used > 0) {
478             redisLog(REDIS_DEBUG,"DB %d: %d keys in %d slots HT.",j,used,size);
479             // dictPrintStats(server.dict);
480         }
481         if (size && used && size > REDIS_HT_MINSLOTS &&
482             (used*100/size < REDIS_HT_MINFILL)) {
483             redisLog(REDIS_NOTICE,"The hash table %d is too sparse, resize it...",j);
484             dictResize(server.dict[j]);
485             redisLog(REDIS_NOTICE,"Hash table %d resized.",j);
486         }
487     }
488 
489     /* Show information about connected clients */
490     if (!(loops % 5)) redisLog(REDIS_DEBUG,"%d clients connected",listLength(server.clients));
491 
492     /* Close connections of timedout clients */
493     if (!(loops % 10))
494         closeTimedoutClients();
495 
496     /* Check if a background saving in progress terminated */
497     if (server.bgsaveinprogress) {
498         int statloc;
499         if (wait4(-1,&statloc,WNOHANG,NULL)) {
500             int exitcode = WEXITSTATUS(statloc);
501             if (exitcode == 0) {
502                 redisLog(REDIS_NOTICE,
503                     "Background saving terminated with success");
504                 server.dirty = 0;
505                 server.lastsave = time(NULL);
506             } else {
507                 redisLog(REDIS_WARNING,
508                     "Background saving error");
509             }
510             server.bgsaveinprogress = 0;
511         }
512     } else {
513         /* If there is not a background saving in progress check if
514          * we have to save now */
515          time_t now = time(NULL);
516          for (j = 0; j < server.saveparamslen; j++) {
517             struct saveparam *sp = server.saveparams+j;
518 
519             if (server.dirty >= sp->changes &&
520                 now-server.lastsave > sp->seconds) {
521                 redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
522                     sp->changes, sp->seconds);
523                 saveDbBackground("dump.rdb");
524                 break;
525             }
526          }
527     }
528     return 1000;
529 }

Line467更新全局的计数值,记录定时事件处理的次数.Line472:487依次检查server.dbnum个(默认是16)内存数据库(是一个哈希表数据结构),Line475:476获得该内存数据库的size和used的值,这里的size也就是哈系表结构中的BUCKETS.在redis使用过程中,used可能小于,等于或者大于size的值.Line481:486检查如果哈希表比较稀疏,将调用dictResize调整其容量,该函数的实现为(dict.c):

113 /* Resize the table to the minimal size that contains all the elements,
114  * but with the invariant of a USER/BUCKETS ration near to <= 1 */
115 int dictResize(dict *ht)
116 {
117     int minimal = ht->used;
118 
119     if (minimal < DICT_HT_INITIAL_SIZE)
120         minimal = DICT_HT_INITIAL_SIZE;
121     return dictExpand(ht, minimal);
122 }

调整的规则是,使size大小尽可能接近与used. 在函数dictExpand会看到, size的值是2的幂,所以sized可能大于或者等于used.函数dictExpand在处理redis协议命令时再具体分析.

回到函数serverCron, Line493:494尝试终止空闲的客户端连接,函数closeTimedoutClients的实现为(redis.c):

448 void closeTimedoutClients(void) {
449     redisClient *c;
450     listIter *li;
451     listNode *ln;
452     time_t now = time(NULL);
453 
454     li = listGetIterator(server.clients,AL_START_HEAD);
455     if (!li) return;
456     while ((ln = listNextElement(li)) != NULL) {
457         c = listNodeValue(ln);
458         if (now - c->lastinteraction > server.maxidletime) {
459             redisLog(REDIS_DEBUG,"Closing idle client");
460             freeClient(c);
461         }
462     }
463     listReleaseIterator(li);
464 }

首先Line454:455创建了一个链表遍历器来访问全局的客户端对象链表server.clients,因为该链表是个list类型,是个双向链表,因此遍历器在创建的时候可以指定从头访问该链表或者从尾部访问该链表.Line456:462依次访问链表中的客户端对象,并检查其空闲时间是否超过了系统配置时间server.maxidletime(默认是5分钟),如果是,则调用函数freeClient终止该客户端连接.最后Line463释放动态创建的遍历器对象.函数freeClient的实现为(redis.c):

701 static void freeClient(redisClient *c) {
702     listNode *ln;
703 
704     aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
705     aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
706     sdsfree(c->querybuf);
707     listRelease(c->reply);
708     freeClientArgv(c);
709     close(c->fd);
710     ln = listSearchKey(server.clients,c);
711     assert(ln != NULL);
712     listDelNode(server.clients,ln);
713     free(c);
714 }

Line704:705释放该客户端对象相关的文件事件对象,对于不同的触发类型AE_READABLEAE_WRITABLE, 同一客户端对象会创建不同的文件事件对象.Line706:708是释放该客户端对象字段指向的动态内存,以免内存泄漏.Line709关闭socket套接字,释放TCP连接.Line710:712从全局链表中删除该客户端对象.Line713释放该客户端对象的内存,因为客户端对象都是由堆上动态分配的内存.

回到函数serverCron, Line496:527是对于保存内存数据到磁盘的相关处理.Line497检查是否子进程正在写磁盘操作,如果是,阻塞当前进程等待子进程写磁盘操作结束.子进程成功结束后,当前进程会更新全局对象server,设置内存数据脏标识为0,设置写磁盘时间戳为当前时间,并且清除正在写磁盘标识.如果没有子进程正在写磁盘操作,Line516:526判断当前是否可以进行异步的写磁盘操作,有三个默认的判断标准,如果一小时之内内存数据库发生过至少一次的更新操作或者如果五分钟之内内存数据库发生过至少100次的更新操作或者一分钟之内内存数据库至少发生过10000次的更系操作,满足任何一个标准即调用函数saveDbBackground进行异步写磁盘操作.该函数的实现为(redis.c):

1169 static int saveDbBackground(char *filename) {
1170     pid_t childpid;
1171 
1172     if (server.bgsaveinprogress) return REDIS_ERR;
1173     if ((childpid = fork()) == 0) {
1174         /* Child */
1175         close(server.fd);
1176         if (saveDb(filename) == REDIS_OK) {
1177             exit(0);
1178         } else {
1179             exit(1);
1180         }
1181     } else {
1182         /* Parent */
1183         redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);
1184         server.bgsaveinprogress = 1;
1185         return REDIS_OK;
1186     }
1187     return REDIS_OK; /* unreached */
1188 }

Line1173:1181创建子进程,并调用函数saveDb进行写磁盘操作,该函数返回后,结束子进程.Line1182:1185仍然处于可运行状态,设置全局的server.bgsaveinprogress标识并返回.

函数saveDb的实现为(redis.c):

1055 /* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
1056 static int saveDb(char *filename) {
1057     dictIterator *di = NULL;
1058     dictEntry *de;
1059     uint32_t len;
1060     uint8_t type;
1061     FILE *fp;
1062     char tmpfile[256];
1063     int j;
1064 
1065     snprintf(tmpfile,256,"temp-%d.%ld.rdb",(int)time(NULL),(long int)random());
1066     fp = fopen(tmpfile,"w");
1067     if (!fp) {
1068         redisLog(REDIS_WARNING, "Failed saving the DB: %s", strerror(errno));
1069         return REDIS_ERR;
1070     }
1071     if (fwrite("REDIS0000",9,1,fp) == 0) goto werr;
1072     for (j = 0; j < server.dbnum; j++) {
1073         dict *d = server.dict[j];
1074         if (dictGetHashTableUsed(d) == 0) continue;
1075         di = dictGetIterator(d);
1076         if (!di) {
1077             fclose(fp);
1078             return REDIS_ERR;
1079         }
1080 
1081         /* Write the SELECT DB opcode */
1082         type = REDIS_SELECTDB;
1083         len = htonl(j);
1084         if (fwrite(&type,1,1,fp) == 0) goto werr;
1085         if (fwrite(&len,4,1,fp) == 0) goto werr;
1086 
1087         /* Iterate this DB writing every entry */
1088         while((de = dictNext(di)) != NULL) {
1089             sds key = dictGetEntryKey(de);
1090             robj *o = dictGetEntryVal(de);
1091 
1092             type = o->type;
1093             len = htonl(sdslen(key));
1094             if (fwrite(&type,1,1,fp) == 0) goto werr;
1095             if (fwrite(&len,4,1,fp) == 0) goto werr;
1096             if (fwrite(key,sdslen(key),1,fp) == 0) goto werr;
1097             if (type == REDIS_STRING) {
1098                 /* Save a string value */
1099                 sds sval = o->ptr;
1100                 len = htonl(sdslen(sval));
1101                 if (fwrite(&len,4,1,fp) == 0) goto werr;
1102                 if (sdslen(sval) &&
1103                     fwrite(sval,sdslen(sval),1,fp) == 0) goto werr;
1104             } else if (type == REDIS_LIST) {
1105                 /* Save a list value */
1106                 list *list = o->ptr;
1107                 listNode *ln = list->head;
1108 
1109                 len = htonl(listLength(list));
1110                 if (fwrite(&len,4,1,fp) == 0) goto werr;
1111                 while(ln) {
1112                     robj *eleobj = listNodeValue(ln);
1113                     len = htonl(sdslen(eleobj->ptr));
1114                     if (fwrite(&len,4,1,fp) == 0) goto werr;
1115                     if (sdslen(eleobj->ptr) && fwrite(eleobj->ptr,sdslen(eleobj->ptr),1,fp) == 0)
1116                         goto werr;
1117                     ln = ln->next;
1118                 }
1119             } else if (type == REDIS_SET) {
1120                 /* Save a set value */
1121                 dict *set = o->ptr;
1122                 dictIterator *di = dictGetIterator(set);
1123                 dictEntry *de;
1124 
1125                 if (!set) oom("dictGetIteraotr");
1126                 len = htonl(dictGetHashTableUsed(set));
1127                 if (fwrite(&len,4,1,fp) == 0) goto werr;
1128                 while((de = dictNext(di)) != NULL) {
1129                     robj *eleobj;
1130 
1131                     eleobj = dictGetEntryKey(de);
1132                     len = htonl(sdslen(eleobj->ptr));
1133                     if (fwrite(&len,4,1,fp) == 0) goto werr;
1134                     if (sdslen(eleobj->ptr) && fwrite(eleobj->ptr,sdslen(eleobj->ptr),1,fp) == 0)
1135                         goto werr;
1136                 }
1137                 dictReleaseIterator(di);
1138             } else {
1139                 assert(0 != 0);
1140             }
1141         }
1142         dictReleaseIterator(di);
1143     }
1144     /* EOF opcode */
1145     type = REDIS_EOF;
1146     if (fwrite(&type,1,1,fp) == 0) goto werr;
1147     fclose(fp);
1148    
1149     /* Use RENAME to make sure the DB file is changed atomically only
1150      * if the generate DB file is ok. */
1151     if (rename(tmpfile,filename) == -1) {
1152         redisLog(REDIS_WARNING,"Error moving temp DB file on the final destionation: %s", strerror(errno));
1153         unlink(tmpfile);
1154         return REDIS_ERR;
1155     }
1156     redisLog(REDIS_NOTICE,"DB saved on disk");
1157     server.dirty = 0;
1158     server.lastsave = time(NULL);
1159     return REDIS_OK;
1160 
1161 werr:
1162     fclose(fp);
1163     unlink(tmpfile);
1164     redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
1165     if (di) dictReleaseIterator(di);
1166     return REDIS_ERR;
1167 }

在分析写磁盘操作之前,说明一下redis内存数据库的逻辑结构,redis是一个存储key/value的NoSQL类型的数据库,key的类型是string,redis中的string其实就是二进制,只不过不不包含’\n’和空格,因为这两个字节被用作解析redis协议命令的分隔符.value支持三种类型,包括string,listset,其中listset是复合类型.redis的内存数据库的逻辑结构由全局对象serverdict字段管理,涉及到的相关类型有dict,dictType,sds,sdshdr,robj

类型dict的定义为(dict.h):

31 typedef struct dict {
32     dictEntry **table;
33     dictType *type;
34     unsigned int size;
35     unsigned int sizemask;
36     unsigned int used;
37     void *privdata;
38 } dict;

字段table组织成一个哈希表结构,其BUCKETS的数量即字段size的值,size的取值为16,32,64数列.字段sizemask的值为size-1. 字段used为哈希表中节点的个数,需要说明的是节点的个数指的是key的个数,因为节点有可能是复合类型listset,同一个key下面有多个子节点.字段type包含一组函数指针,用于操作哈希表中的节点.

类型dictType的定义(dict.h):

22 typedef struct dictType {
23     unsigned int (*hashFunction)(const void *key);
24     void *(*keyDup)(void *privdata, const void *key);
25     void *(*valDup)(void *privdata, const void *obj);
26     int (*keyCompare)(void *privdata, const void *key1, const void *key2);
27     void (*keyDestructor)(void *privdata, void *key);
28     void (*valDestructor)(void *privdata, void *obj);
29 } dictType;

这一组函数指针用来操作哈希表中的节点,比如哈希函数hashFunction会根据key的值映射到指定索引的BUCKET上,其他节点操作还包括key和value的复制,key的比较,key和value的销毁操作等.

类型dictEntry的定义为(dict.h):

16 typedef struct dictEntry {
17     void *key;
18     void *val;
19     struct dictEntry *next;
20 } dictEntry;

类型dictEntry标识哈希表中的节点.当不同节点的key具有相同的哈希值,这些冲突节点就以链表的形式组织起来.字段key的类型为sds,字段value的类型为robj.字段next指向冲突链表中的下一个节点.

类型sds其实就是一个char *指针,通常和类型sdshdr配合使用. 该类型的定义为(sds.h):

33 typedef char *sds;
34 
35 struct sdshdr {
36     long len;
37     long free;
38     char buf[0];
39 };

类型sdshdr是类型sds的封装和管理类型,sds其实指向sdshdr类型对象的buf字段.字段lenbuf中存储的字节数,字段freebuf中空闲的字节数.

类型robj标识redis对象,哈希表节点中的val字段的其实指向的就是robj类型的对象.类型robj的定义为(redis.c):

81 /* A redis object, that is a type able to hold a string / list / set */
82 typedef struct redisObject {
83     int type;
84     void *ptr;
85     int refcount;
86 } robj;

字段type标识该对象的类型,其取值可以为REDIS_STRING, REDIS_LISTREDIS_SET,也就是redis数据库的key/value存储的value的三种类型.字段refcount记录该对象的引用计数,当其值为0时, 需要释放该robj对象.字段ptr根据type的类型分别指向sds,listdict对象.

sds对象为空时,其sdshdr类型的lenfree字段均为0,如下图所示:
这里写图片描述

全局内存数据库的逻辑结构如下图所示:
这里写图片描述
哈希节点类型dictEntry的字段val是个robj对象,如果该对象类型是list或者set,那么每个哈希节点将是个复合结构.

回到函数saveDb, Line1065:1070以写入的方式打开一个临时文件,这样可以保证该文件之前在磁盘上是不存在的.Line1071写入9个字节的签名信息.Line1072:1143依次将内存中的16个数据库数据写入磁盘文件.如果当前内存数据库中没有数据则检查下一个内存数据库.如果当前内存数据库中有数据,则创建一个数据库遍历器,该遍历器初始化操作时,指向当前的数据库,遍历器的作用就是以此取出该数据库中的节点.Line1081:1085写入一个字节的类型标识,和四个字节的数据库索引.函数dictGetIterator用来创建一个哈希表遍历器,并初始化该遍历器的对象,使该遍历器对象指向当前哈希数据库对象.哈希表遍历器的定义为(dict.h):

typedef struct dictIterator {
    dict *ht;
    int index;
    dictEntry *entry, *nextEntry;
} dictIterator;

字段ht为遍历器对象指向的哈希表对象,字段index为正在访问的哈希表的BUCKET的索引,字段entrynextEntry分别指向哈希数据库中当前节点和下一个节点.

Line1088调用函数dictNext循环取得该哈希数据库中的节点.该函数的实现为(dict.c):

316 dictEntry *dictNext(dictIterator *iter)
317 {
318     while (1) {
319         if (iter->entry == NULL) {
320             iter->index++;
321             if (iter->index >=
322                     (signed)iter->ht->size) break;
323             iter->entry = iter->ht->table[iter->index];
324         } else {
325             iter->entry = iter->nextEntry;
326         }
327         if (iter->entry) {
328             /* We need to save the 'next' here, the iterator user
329              * may delete the entry we are returning. */
330             iter->nextEntry = iter->entry->next;
331             return iter->entry;
332         }
333     }
334     return NULL;
335 }

实现逻辑是依次遍历哈希表中的BUCKET,如果当前索引的BUCKET有节点,则依次取得节点,并把一下节点保存到字段nextEntry中.
Line1089:1092取得当前节点的keyval字段,之前介绍过, 这两个字段分别是sdsrobj类型的对象,并取得robj对象的封装的实际数据的类型.Line1093取得key字段的保存的字节数.Line1094:1096分别将value的实际类型和key的长度及key的具体数据写入磁盘文件.Line1097:1137分别根据value的实际类型将string类型或者list类型或者set类型的对象写入磁盘文件.Line1098:1103写入string类型的value时,先写入其长度再写入具体数据.Line1105:1117写入list对象时,先写入list对象中节点的个数,再依次写入节点元数据和节点内容.Line1120:1137写入set类型的value时,先写入哈希表中节点的个数,然后再依次写入节点元数据和节点内容.注意,写入set类型的value时, 是不需要写入哈希表当前的BUCKET值的.Line1144:1147当所有内存数据都写入磁盘以后,再写入一个字节的结束标识并关闭文件.Line1149:1155将该临时文件重命名为正式名称dump.rdb.Line1157:1159写入磁盘文件成功后,更新全局对象,标识内存数据库为干净的,并设置写磁盘的时间戳.

回到定时事件处理函数serverCron,Line528返回1000这个值,该函数返回后,回到事件循环处理主函数aeProcessEvents,Line299:303根据函数serverCron的返回值进行相应的处理.函数aeDeleteTimeEvent是删除该定时事件对象,函数aeAddMillisecondsToNow是更新该定时事件对象的超时时间,根据该函数的实现,定时事件总是每隔一秒触发.当定时事件链表处理完毕以后,函数aeProcessEvents返回,并再次被调用,重新监听和处理事件.

redis服务器的文件事件对象被触发时, 最先接收到的是客户端的连接请求,因为redis使用TCP协议,所以可以使用telnet模拟客户端的连接请求, 假设redis服务器实例运行在地址为10.7.7.132. 监听的是默认端口6379.

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.

客户端已经和redis服务器建立了连接,并且redis服务器已经在监听该客户端的文件事件,等待客户端发送redis协议的命令.

猜你喜欢

转载自blog.csdn.net/azurelaker/article/details/81394277