redis 内部有一个小型的事件驱动ae,它和 libevent 网络库的事件驱动一样,都是依托 I/O 多路复用
利用 I/O 多路复用技术,监听感兴趣的文件 I/O 事件,例如读事件,写事件等,同时也要维护一个以文件描述符为主键,数据为某个预设函数的事件表,这里其实就是一个数组或者链表 。当事件触发时,比如某个文件描述符可读,系统会返回文件描述符值,用这个值在事件表中找到相应的数据项,从而实现回调。同样的,定时事件也是可以实现的,因为系统提供的 I/O 多路复用技术中的函数允许我们设定时间值
与事件驱动有关的数据结构
1: /* File event structure
2: *3: * 文件事件结构4: */5: typedef struct aeFileEvent {6: // 事件类型掩码,值可以是 AE_READABLE 或 AE_WRITABLE ,或者两者的或
7: int mask; /* one of AE_(READABLE|WRITABLE) */8: // 读事件函数
9: aeFileProc *rfileProc;10: // 写事件函数
11: aeFileProc *wfileProc;12: // 多路复用库的私有数据
13: void *clientData;
14: } aeFileEvent;
1:2: /* Time event structure
3: *4: * 时间事件结构5: */6: typedef struct aeTimeEvent {7:8: // 时间事件的唯一标识符
9: long long id; /* time event identifier. */10:11: // 事件的到达时间
12: long when_sec; /* seconds */13: long when_ms; /* milliseconds */14:15: // 事件处理函数
16: aeTimeProc *timeProc;17:18: // 事件释放函数
19: aeEventFinalizerProc *finalizerProc;20:21: // 多路复用库的私有数据
22: void *clientData;
23:24: // 指向下个时间事件结构,形成链表
25: struct aeTimeEvent *next;
26:27: } aeTimeEvent;
1:2: /* A fired event
3: *4: * 已就绪事件5: */6: typedef struct aeFiredEvent {7: // 已就绪文件描述符
8: int fd;
9: // 事件类型掩码,可以是 AE_READABLE 或 AE_WRITABLE
10: int mask;
11: } aeFiredEvent;
1:2: /* State of an event based program
3: *4: * 事件处理器的状态5: */6: typedef struct aeEventLoop {7: // 目前已注册的最大描述符
8: int maxfd; /* highest file descriptor currently registered */9: // 目前已追踪的最大描述符
10: int setsize; /* max number of file descriptors tracked */11: // 用于生成时间事件 id
12: long long timeEventNextId;13: // 最后一次执行时间事件的时间
14: time_t lastTime; /* Used to detect system clock skew */
15: // 已注册的文件事件
16: aeFileEvent *events; /* Registered events */
17: // 已就绪的文件事件
18: aeFiredEvent *fired; /* Fired events */
19: // 时间事件
20: aeTimeEvent *timeEventHead;21: // 事件处理器的开关
22: int stop;
23: // 多路复用库的私有数据
24: void *apidata; /* This is used for polling API specific data */25: // 在处理事件前要执行的函数
26: aeBeforeSleepProc *beforesleep;27: } aeEventLoop;
redis事件库主要关注两种事件:文件事件、时间事件
Redis 将因为对套接字进行多路复用而产生的事件称为文件事件(file event),文件事件可
以分为读事件和写事件两类
时间事件记录着那些要在指定时间点运行的事件,多个时间事件以无序链表的形式保存在服务
器状态中
事件循环中心
在上文介绍的Redis运行流程中介绍到读取完配置文件后进行initServer(),在这个函数中会进行事件结构的初始化
// 初始化事件状态
server.el = aeCreateEventLoop(server.maxclients+1024);
下面分析aeCreateEventLoop这个函数
1:2: /*
3: * 初始化事件处理器状态4: */5: aeEventLoop *aeCreateEventLoop(int setsize) {
6: aeEventLoop *eventLoop;7: int i;
8:9: // 创建事件状态结构
10: if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;11:12: // 初始化文件事件结构和已就绪文件事件结构
13: eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
14: eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
15: if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;16: eventLoop->setsize = setsize;17: eventLoop->lastTime = time(NULL);
18:19: // 初始化时间事件结构
20: eventLoop->timeEventHead = NULL;21: eventLoop->timeEventNextId = 0;22:23: eventLoop->stop = 0;24: eventLoop->maxfd = -1;25: eventLoop->beforesleep = NULL;26: //选择IO复用模型(epoll),对应不同的结构体
27: if (aeApiCreate(eventLoop) == -1) goto err;28: /* Events with mask == AE_NONE are not set. So let's initialize the
29: * vector with it. */30: for (i = 0; i < setsize; i++)
31: eventLoop->events[i].mask = AE_NONE;32: return eventLoop;
33:34: err:35: if (eventLoop) {
36: zfree(eventLoop->events);37: zfree(eventLoop->fired);38: zfree(eventLoop);39: }40: return NULL;
41: }
上面的函数中选择了合适的IO复用模型,并且调用aeApiCreate函数对IO复用进行初始化,具体函数如下
1: static int aeApiCreate(aeEventLoop *eventLoop) {2: aeApiState *state = zmalloc(sizeof(aeApiState));
3:4: if (!state) return -1;5: state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);6: if (!state->events) {
7: zfree(state);8: return -1;
9: }10: state->epfd = epoll_create(1024); /* 1024 is just an hint for the kernel */
11: if (state->epfd == -1) {
12: zfree(state->events);13: zfree(state);14: return -1;
15: }16: eventLoop->apidata = state;17: return 0;
18: }19:
事件的注册
上述函数只是完成事件驱动的初始化,当完成初始化后,会调用anetTcpServer进行套接字的监听,并且返回该监听套接字,之后便可以将该套接字与一文件事件关联,并将其注册到事件驱动的循环中
1:2: /*
3: * 根据 mask 参数的值,监听 fd 文件的状态,4: * 当 fd 可用时,执行 proc 函数5: */6: int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,7: aeFileProc *proc, void *clientData)
8: {9: if (fd >= eventLoop->setsize) return AE_ERR;10: aeFileEvent *fe = &eventLoop->events[fd];11:12: // 监听指定 fd,调用epoll_ctl
13: if (aeApiAddEvent(eventLoop, fd, mask) == -1)
14: return AE_ERR;
15:16: // 设置文件事件类型
17: fe->mask |= mask;18: if (mask & AE_READABLE) fe->rfileProc = proc;
19: if (mask & AE_WRITABLE) fe->wfileProc = proc;
20:21: fe->clientData = clientData;22:23: // 如果有需要,更新事件处理器的最大 fd
24: if (fd > eventLoop->maxfd)
25: eventLoop->maxfd = fd;26:27: return AE_OK;
28: }
在完成文件事件注册之前,会创建时间事件,在aeCreateTimeEvent() 中完成:分配时间事件结构体,设置触发时间和回调函数,插入到定时事件表中
1:2: /*
3: * 创建时间事件4: */5: long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,6: aeTimeProc *proc, void *clientData,
7: aeEventFinalizerProc *finalizerProc)8: {9: // 更新时间计数器
10: long long id = eventLoop->timeEventNextId++;11: aeTimeEvent *te;12:13: te = zmalloc(sizeof(*te));
14: if (te == NULL) return AE_ERR;15:16: te->id = id;17:18: // 设定处理事件的时间,
19: aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);20: te->timeProc = proc;//正常模式下的 Redis 一般只带有 serverCron事件
21: te->finalizerProc = finalizerProc;22: te->clientData = clientData;23:24: // 将新事件放入表头
25: te->next = eventLoop->timeEventHead;26: eventLoop->timeEventHead = te;27:28: return id;
29: }30:
在redis运行初始化时,注册的时间事件只有一个serverCron
对于持续运行的服务器来说,服务器需要定期对自身的资源和状态进行必要的检查和整理,从而让服务器维持在一个健康稳定的状态,这类操作被
统称为常规操作(cron job)
在 Redis 中,常规操作由 redis.c/serverCron 实现,它主要执行以下操作:
• 更新服务器的各类统计信息,比如时间、内存占用、数据库占用情况等
• 清理数据库中的过期键值对。
• 对不合理的数据库进行大小调整。(rehash)
• 关闭和清理连接失效的客户端。
• 尝试进行 AOF 或 RDB 持久化操作。
• 如果服务器是主节点的话,对附属节点进行定期同步。
• 如果处于集群模式的话,对集群进行定期同步和连接测试
事件循环(事件分发)
当执行完initServer之后,会重新回到main函数,在这时便可以进入到事件循环,首先会调用aeMain
1: // 事件处理器的主循环
2: void aeMain(aeEventLoop *eventLoop) {
3:4: eventLoop->stop = 0;5:6: while (!eventLoop->stop) {
7:8: // 如果有需要在事件处理前执行的函数,那么运行它
9: if (eventLoop->beforesleep != NULL)
10: eventLoop->beforesleep(eventLoop);11:12: // 开始处理事件
13: aeProcessEvents(eventLoop, AE_ALL_EVENTS);14: }15: }16:
真正的事件循环是在aeProcessEvents中进行的
1:2: /* Process every pending time event, then every pending file event
3: * (that may be registered by time event callbacks just processed).4: *5: * 处理所有已到达的时间事件,以及所有已就绪的文件事件。6: *7: * Without special flags the function sleeps until some file event8: * fires, or when the next time event occurrs (if any).9: *10: * 如果不传入特殊 flags 的话,那么函数睡眠直到文件事件就绪,11: * 或者下个时间事件到达(如果有的话)。12: *13: * If flags is 0, the function does nothing and returns.14: * 如果 flags 为 0 ,那么函数不作动作,直接返回。15: *16: * if flags has AE_ALL_EVENTS set, all the kind of events are processed.17: * 如果 flags 包含 AE_ALL_EVENTS ,所有类型的事件都会被处理。18: *19: * if flags has AE_FILE_EVENTS set, file events are processed.20: * 如果 flags 包含 AE_FILE_EVENTS ,那么处理文件事件。21: *22: * if flags has AE_TIME_EVENTS set, time events are processed.23: * 如果 flags 包含 AE_TIME_EVENTS ,那么处理时间事件。24: *25: * if flags has AE_DONT_WAIT set the function returns ASAP until all26: * the events that's possible to process without to wait are processed.27: * 如果 flags 包含 AE_DONT_WAIT ,28: * 那么函数在处理完所有不许阻塞的事件之后,即刻返回。29: *30: * The function returns the number of events processed.31: * 函数的返回值为已处理事件的数量32: */33: int aeProcessEvents(aeEventLoop *eventLoop, int flags)34: {35: int processed = 0, numevents;
36:37: /* Nothing to do? return ASAP */
38: if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;39:40: /* Note that we want call select() even if there are no
41: * file events to process as long as we want to process time42: * events, in order to sleep until the next time event is ready43: * to fire. */44: if (eventLoop->maxfd != -1 ||
45: ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {46: int j;
47: aeTimeEvent *shortest = NULL;48: struct timeval tv, *tvp;
49:50: // 获取最近的时间事件
51: if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
52: shortest = aeSearchNearestTimer(eventLoop);53: if (shortest) {
54: // 如果时间事件存在的话
55: // 那么根据最近可执行时间事件和现在时间的时间差来决定文件事件的阻塞时间
56: long now_sec, now_ms;
57:58: /* Calculate the time missing for the nearest
59: * timer to fire. */60: // 计算距今最近的时间事件还要多久才能达到
61: // 并将该时间距保存在 tv 结构中
62: aeGetTime(&now_sec, &now_ms);63: tvp = &tv;64: tvp->tv_sec = shortest->when_sec - now_sec;65: if (shortest->when_ms < now_ms) {
66: tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;67: tvp->tv_sec --;68: } else {
69: tvp->tv_usec = (shortest->when_ms - now_ms)*1000;70: }71:72: // 时间差小于 0 ,说明事件已经可以执行了,将秒和毫秒设为 0 (不阻塞)
73: if (tvp->tv_sec < 0) tvp->tv_sec = 0;
74: if (tvp->tv_usec < 0) tvp->tv_usec = 0;
75: } else {
76:77: // 执行到这一步,说明没有时间事件
78: // 那么根据 AE_DONT_WAIT 是否设置来决定是否阻塞,以及阻塞的时间长度
79:80: /* If we have to check for events but need to return
81: * ASAP because of AE_DONT_WAIT we need to se the timeout82: * to zero */83: if (flags & AE_DONT_WAIT) {
84: // 设置文件事件不阻塞
85: tv.tv_sec = tv.tv_usec = 0;86: tvp = &tv;87: } else {
88: /* Otherwise we can block */
89: // 文件事件可以阻塞直到有事件到达为止
90: tvp = NULL; /* wait forever */
91: }92: }93:94: // 处理文件事件,阻塞时间由 tvp 决定
95: numevents = aeApiPoll(eventLoop, tvp);96: for (j = 0; j < numevents; j++) {
97: // 从已就绪数组中获取事件
98: aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];99:100: int mask = eventLoop->fired[j].mask;
101: int fd = eventLoop->fired[j].fd;
102: int rfired = 0;
103:104: /* note the fe->mask & mask & ... code: maybe an already processed
105: * event removed an element that fired and we still didn't106: * processed, so we check if the event is still valid. */107: if (fe->mask & mask & AE_READABLE) {
108: // 读事件
109: rfired = 1; // 确保读/写事件只能执行其中一个
110: fe->rfileProc(eventLoop,fd,fe->clientData,mask);111: }112: if (fe->mask & mask & AE_WRITABLE) {
113: // 写事件
114: if (!rfired || fe->wfileProc != fe->rfileProc)
115: fe->wfileProc(eventLoop,fd,fe->clientData,mask);116: }117:118: processed++;119: }120: }121:122: /* Check time events */
123: // 执行时间事件
124: if (flags & AE_TIME_EVENTS)
125: processed += processTimeEvents(eventLoop);126:127: return processed; /* return the number of processed file/time events */128: }129:
以上函数主要进行了三种工作:
1.遍历时间事件链表中的事件,选择出距离触发最短的一个事件,记作shortest,计算需要等待的时间tev
2.调用aeApiPoll() 进入监听轮询,在这个函数中会调用epoll_wait,并且将超时时间设置为上一步计算出的tev
3.处理已经触发的文件事件和时间事件
其中处理事件事件的函数为processTimeEvents,该函数返回一共处理了多少个时间事件
1:2: /* Process time events
3: *4: * 处理所有已到达的时间事件5: */6: static int processTimeEvents(aeEventLoop *eventLoop) {7: int processed = 0;
8: aeTimeEvent *te;9: long long maxId;10: time_t now = time(NULL);
11:12: /* If the system clock is moved to the future, and then set back to the
13: * right value, time events may be delayed in a random way. Often this14: * means that scheduled operations will not be performed soon enough.15: *16: * Here we try to detect system clock skews, and force all the time17: * events to be processed ASAP when this happens: the idea is that18: * processing events earlier is less dangerous than delaying them19: * indefinitely, and practice suggests it is. */20: // 通过重置事件的运行时间,
21: // 防止因时间穿插(skew)而造成的事件处理混乱
22: if (now < eventLoop->lastTime) {
23: te = eventLoop->timeEventHead;24: while(te) {
25: te->when_sec = 0;26: te = te->next;27: }28: }29: // 更新最后一次处理时间事件的时间
30: eventLoop->lastTime = now;31:32: te = eventLoop->timeEventHead;33: maxId = eventLoop->timeEventNextId-1;34: while(te) {
35: long now_sec, now_ms;
36: long long id;37:38: // 跳过无效事件
39: if (te->id > maxId) {
40: te = te->next;41: continue;
42: }43:44: // 获取当前时间
45: aeGetTime(&now_sec, &now_ms);46:47: // 如果当前时间等于或等于事件的执行时间,那么执行这个事件
48: if (now_sec > te->when_sec ||
49: (now_sec == te->when_sec && now_ms >= te->when_ms))50: {51: int retval;
52:53: id = te->id;54: //此处调用serverCron
55: retval = te->timeProc(eventLoop, id, te->clientData);56: processed++;57: /* After an event is processed our time event list may
58: * no longer be the same, so we restart from head.59: * Still we make sure to don't process events registered60: * by event handlers itself in order to don't loop forever.61: * To do so we saved the max ID we want to handle.62: *63: * FUTURE OPTIMIZATIONS:64: * Note that this is NOT great algorithmically. Redis uses65: * a single time event so it's not a problem but the right66: * way to do this is to add the new elements on head, and67: * to flag deleted elements in a special way for later68: * deletion (putting references to the nodes to delete into69: * another linked list). */70:71: // 记录是否有需要循环执行这个事件时间
72: if (retval != AE_NOMORE) {
73: // 是的, retval 毫秒之后继续执行这个时间事件
74: aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);75: } else {
76: // 不,将这个事件删除
77: aeDeleteTimeEvent(eventLoop, id);78: }79:80: // 因为执行事件之后,事件列表可能已经被改变了
81: // 因此需要将 te 放回表头,继续开始执行事件
82: te = eventLoop->timeEventHead;83: } else {
84: te = te->next;85: }86: }87: return processed;
88: }89:
redis 的事件驱动总结如下:
- 初始化事件循环结构体
- 注册监听套接字的读事件
- 注册定时事件
- 进入事件循环
- 如果监听套接字变为可读,会接收客户端请求,并为对应的套接字注册读事件
- 如果与客户端连接的套接字变为可读,执行相应的操作