首先介绍下网络编程经典模型
Reactor in Single Thread
Reactor in Single Thread: 采用IO多路复用机制(epoll,select,poll)。我们抽取出一个单线程版的reactor模型,时序图见下文,该方案只有一个线程,所有的socket连接均注册在了该reactor上,由一个线程全权负责所有的任务。它实现简单,且不受线程数的限制。这种方案受限于使用场景,仅适合于IO密集的应用,不太适合CPU密集的应用,且适合于CPU资源紧张的应用上。
redis网络部分源码主要集中在ae.h ae.c中,其中封装了一个小型的事件库。整个网络部分的流程为
简单说来Redis使用的事件处理机制就是通过一个主aeMain循环在单线程执行,在每一次循环中首先查看是否需要有其他阻塞的客户端或者是aof需要执行。然后在具体的aeProcessEvents中则根据传递的参数判断如何处理文件事件和时间事件
- /* 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 occurrs (if any).
- *
- * 如果不传入特殊 flags 的话,那么函数睡眠直到文件事件就绪,
- * 或者下个时间事件到达(如果有的话)。
- *
- * If flags is 0, the function does nothing and returns.
- * 如果 flags 为 0 ,那么函数不作动作,直接返回。
- *
- * if flags has AE_ALL_EVENTS set, all the kind of events are processed.
- * 如果 flags 包含 AE_ALL_EVENTS ,所有类型的事件都会被处理。
- *
- * if flags has AE_FILE_EVENTS set, file events are processed.
- * 如果 flags 包含 AE_FILE_EVENTS ,那么处理文件事件。
- *
- * if flags has AE_TIME_EVENTS set, time events are processed.
- * 如果 flags 包含 AE_TIME_EVENTS ,那么处理时间事件。
- *
- * 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.
- * 如果 flags 包含 AE_DONT_WAIT ,
- * 那么函数在处理完所有不许阻塞的事件之后,即刻返回。
- *
- * 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. */
- // 计算距今最近的时间事件还要多久才能达到
- // 并将该时间距保存在 tv 结构中
- 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;
- }
- // 时间差小于 0 ,说明事件已经可以执行了,将秒和毫秒设为 0 (不阻塞)
- if (tvp->tv_sec < 0) tvp->tv_sec = 0;
- if (tvp->tv_usec < 0) tvp->tv_usec = 0;
- } else {
- // 执行到这一步,说明没有时间事件
- // 那么根据 AE_DONT_WAIT 是否设置来决定是否阻塞,以及阻塞的时间长度
- /* If we have to check for events but need to return
- * ASAP because of AE_DONT_WAIT we need to se 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 */
- }
- }
- // 处理文件事件,阻塞时间由 tvp 决定
- 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 */
- }
- /* 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. */
- // 通过重置事件的运行时间,
- // 防止因时间穿插(skew)而造成的事件处理混乱
- 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) {
- // 是的, retval 毫秒之后继续执行这个时间事件
- aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
- } else {
- // 不,将这个事件删除
- aeDeleteTimeEvent(eventLoop, id);
- }
- // 因为执行事件之后,事件列表可能已经被改变了
- // 因此需要将 te 放回表头,继续开始执行事件
- te = eventLoop->timeEventHead;
- } else {
- te = te->next;
- }
- }
- return processed;
- }