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刚启动状态下, 只有初始化创建的一个文件事件对象, 其mask
是AE_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
用来存储客户端协议命令及其参数,而字段argc
和argv
代表解析后的协议命令的参数个数和具体的参数值.类型sds
是一个char *类型的指针,用来和类型sdshdr
配合使用,以实现动态string类型对象.字段bulklen
标识bulk类型协议命令中value的字节长度,字段reply
是list
类型的对象,记录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_READABLE
和AE_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
,list
和set
,其中list
和set
是复合类型.redis的内存数据库的逻辑结构由全局对象server
的dict
字段管理,涉及到的相关类型有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的个数,因为节点有可能是复合类型list
和set
,同一个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
字段.字段len
是buf
中存储的字节数,字段free
是buf
中空闲的字节数.
类型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_LIST
和REDIS_SET
,也就是redis数据库的key/value存储的value的三种类型.字段refcount
记录该对象的引用计数,当其值为0时, 需要释放该robj
对象.字段ptr
根据type
的类型分别指向sds
,list
和dict
对象.
sds
对象为空时,其sdshdr
类型的len
和free
字段均为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的索引,字段entry
和nextEntry
分别指向哈希数据库中当前节点和下一个节点.
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取得当前节点的key
和val
字段,之前介绍过, 这两个字段分别是sds
和robj
类型的对象,并取得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协议的命令.