概述
Redis服务器是一个事件驱动器,服务器需要处理以下两类事件:
文件事件:Redis服务器通过套接字与客户端进行通信,而文件事件就是服务器对套接字操作的抽象。
时间事件:Redis服务器需要定期的执行一些操作,比如serverCron函数,时间操作就是对这类操作的抽象。
文件事件
Redis基于Reactor模式开发了自己的网络事件处理器,这个处理器被称为文件事件处理器。文件事件处理器通过I/O多路复用程序来同时监控多个套接字,并根据套接字不同的类型为套接字关联不同的文件事件。当被监控的套接字准备执行应答、写入、读取、关闭等操作时,与操作对应的文件事件就会产生。
文件事件处理器的四个组成部分:套接字,I/O多路复用程序,文件事件分配器,以及事件处理器。
时间事件
Redis时间事件分为两大类:
定时事件:让一段程序在指定的时间之后执行一次。比如30s后执行某个程序。
周期性事件:让一个程序每个一段周期后执行一次,比如没30s执行一次。
目前在Redis中的实现只有周期事件,没有定时事件的实现。
时间事件的数据结构如下:
/* Time event structure */ typedef struct aeTimeEvent { long long id; /* time event identifier.,全局唯一ID,新的事件ID比旧的事件ID号大 */ long when_sec; /* seconds 时间到达的时间戳*/ long when_ms; /* milliseconds 时间达到的毫秒时间戳*/ aeTimeProc *timeProc; //时间事件处理器,一个函数 aeEventFinalizerProc *finalizerProc; void *clientData; struct aeTimeEvent *next; } aeTimeEvent;
怎么实现时间事件呢?
Redis服务器会将所有的时间事件放入到一个链表,每当时间事件执行器运行时,它就遍历整个链表,查找所有已经到达的时间事件,并调用相应的事件处理器。
如下所示,三个时间事件加入到一个链表:
这里的链表是无序的,链表不按when的属性排序,所以当时间事件执行器运行的时候,必须遍历整个链表,才能保证所有达到的时间事件都能够被处理。在3.0的版本中,服务器只有一个serverCron的时间事件,在这种情况下,服务器几乎是将时间链表退化成一个指针来使用,所以在性能上并不会有什么影响。
serverCron函数主要做哪些处理?
1.更新服务器的各类统计信息,比如时间、内存占用,数据库占用情况等;
2.清理数据库中过期的键值空间;
3.关闭和清理实现的客户端连接;
4.尝试进行AOF或RDB持久化操作;
5.如果是主服务器,会定期的对从服务器进行同步;
6.如果处于集群模式,对集群定期的进行同步和连接测试。
事件调度与执行
服务器中同时存在文件事件和时间事件,服务器应该怎么何时处理文件事件,何时处理时间事件,以及使用多少时间来处理等?
先看下源码:
/* Process every pending time event, then every pending file event * (that may be registered by time event callbacks just processed). * Without special flags the function sleeps until some file event * fires, or when the next time event occurs (if any). * * If flags is 0, the function does nothing and returns. * if flags has AE_ALL_EVENTS set, all the kind of events are processed. * if flags has AE_FILE_EVENTS set, file events are processed. * if flags has AE_TIME_EVENTS set, time events are processed. * if flags has AE_DONT_WAIT set the function returns ASAP until all * the events that's possible to process without to wait are processed. * * The function returns the number of events processed. */ 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; /* Calculate the time missing for the nearest * timer to fire. */ aeGetTime(&now_sec, &now_ms); tvp = &tv; tvp->tv_sec = shortest->when_sec - now_sec; if (shortest->when_ms < now_ms) { tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000; tvp->tv_sec --; } else { tvp->tv_usec = (shortest->when_ms - now_ms)*1000; } if (tvp->tv_sec < 0) tvp->tv_sec = 0; if (tvp->tv_usec < 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 */ } } numevents = aeApiPoll(eventLoop, tvp); 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 rfired = 0; /* 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. */ if (fe->mask & mask & AE_READABLE) { rfired = 1; fe->rfileProc(eventLoop,fd,fe->clientData,mask); } if (fe->mask & mask & AE_WRITABLE) { if (!rfired || fe->wfileProc != fe->rfileProc) fe->wfileProc(eventLoop,fd,fe->clientData,mask); } processed++; } } /* Check time events */ //处理已经到达的时间事件 if (flags & AE_TIME_EVENTS) processed += processTimeEvents(eventLoop); return processed; /* return the number of processed file/time events */ }
大致流程如下:
事件之前不会出现抢占的情况,也就是说在处理文件事件的时候,如果时间事件达到,需要等待文件事件执行完。因为不管是文件事件处理器还是时间事件处理器都会尽可能的减少程序阻塞的时间。并在需要的时候让出主动权。