分类
时间事件分为两类:
定时事件
周期事件
一个时间事件包括三要素:id,when,timeproc(时间事件处理器)
时间事件处理器实现:
服务器将所有的时间事件放在一个无序链表中,运行时,遍历整个链表,调用到时间的事件对应的处理器。
正常情况下,服务器只有serverCron一个时间事件,所以采用无序链表不会影响性能。
serverCron 函数
工作主要包括:
-
更新服务器的统计信息:时间,内存,数据库占用情况
-
清理过期键值对
-
关闭失效的连接
-
尝试进行aof\rdb持久化操作
-
对从服务器进行定期同步
-
集群模式下的定期同步,连接测试
时间事件
数据结构
文件:ae.h
时间事件的三个属性:
when:以毫秒格式的 UNIX 时间戳为单位,记录了应该在什么时间点执行事件处理函数。
timeProc:事件处理函数。
next 指向下一个时间事件,形成链表。
/* Time event structure */
typedef struct aeTimeEvent {
long long id; /* time event identifier. */
long when_sec; /* seconds */
long when_ms; /* milliseconds */
aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
struct aeTimeEvent *next;
} aeTimeEvent;
API
文件:ae.c(v3.0.1 L 203)
函数 aeCreateTimeEvent
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
long long id = eventLoop->timeEventNextId++;
aeTimeEvent *te;
te = zmalloc(sizeof(*te));
if (te == NULL) return AE_ERR;
te->id = id;
aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
te->timeProc = proc;
te->finalizerProc = finalizerProc;
te->clientData = clientData;
te->next = eventLoop->timeEventHead;
eventLoop->timeEventHead = te;
return id;
}
示例 serverCron 使用
redis-server 启动时,Redis 做了很多初始化的工作,这些工作大多是在initServer()这个函数中执行的,初始化一些相关的list,dict等;调用了aeCreateTimeEvent()函数来初始化一下定时器,定期地执行serverCron()这个函数。
文件redis.c(v3.0.1 L1837)
/* Create the serverCron() time event, that's our main way to process
* background operations. */
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
redisPanic("Can't create the serverCron time event.");
exit(1);
}
触发定时任务执行
整个执行流程:
main->aeMain->aeProcessEvents->processTimeEvents->serverCron
文件: ae.c
/* 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) {
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;
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++;
/* After an event is processed our time event list may
* no longer be the same, so we restart from head.
* Still we make sure to don't process events registered
* by event handlers itself in order to don't loop forever.
* To do so we saved the max ID we want to handle.
*
* FUTURE OPTIMIZATIONS:
* Note that this is NOT great algorithmically. Redis uses
* a single time event so it's not a problem but the right
* way to do this is to add the new elements on head, and
* to flag deleted elements in a special way for later
* deletion (putting references to the nodes to delete into
* another linked list). */
if (retval != AE_NOMORE) {
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
} else {
aeDeleteTimeEvent(eventLoop, id);
}
te = eventLoop->timeEventHead;
} else {
te = te->next;
}
}
return processed;
}
根据 timeProc 函数的返回值,可以将时间事件划分为两类:
如果事件处理函数返回 ae.h/AE_NOMORE,那么这个事件为单次执行事件:该事件会在指定的时间被处理一次,之后该事件就会被删除,不再执行。
如果事件处理函数返回一个非 AE_NOMORE 的整数值,那么这个事件为循环执行事件:该事件会在指定的时间被处理,之后它会按照事件处理函数的返回值,更新事件的 when 属性,让这个事件在之后的某个时间点再次运行,并以这种方式一直更新并运行下去。
个人收获
原理
原理非常重要。
一切都是基于多路复用+事件驱动的。