1. 总述

Redis服务器时一个事件驱动程序,服务器主要处理如下的两类事件:

  • 文件事件(file event):服务器和客户端、主服务器和从服务器之间的Socket连接都会产生相应的文件事件。服务器通过对事件的监听来决定执行的操作
  • 时间事件(time event):Redis服务器内部存在一些定时的操作,由此而产生的为时间事件

一切皆文件。

Redis中的文件事件处理器(file event handler)基于Reactor模式构建,既实现了高性能的网络通信模型,又维持了Redis单线程设计的优越性。文件事件处理器会使用IO多路复用器来监听多个Socket连接,根据Socket执行的任务来关联不同的事件处理器。当监听的Socket发生accept、read、write、close等操作时,就会产生相应的文件事件,这时文件事件处理器就会调用与Socket关联的处理器来进行处理。

Reactor模式可以阅读以下的文章进行理解: Reactor模式

Reactor模式详解

Reactor 反应堆设计模式

文件事件处理主要由Socket、IO多路复用器、文件事件分派器、事件处理器和队列组成,如下所示:
image.png

Redis中通常会并发的存在多个连接,对应的就会有多个Socket,这里使用了IO多路复用来监听所有可能的Socket。然后将所有产生事件的Socket放到队列中,以一种有序同步的方法通过文件事件分派器交给具体的事件处理器进行处理。

这里的IO多路复用并不是特指select、epoll、evport、kqueue中的一种,Redis对于这些多路复用函数都进行了包装,每一种都有具体的API,程序在编译时会自动的选择系统中性能最高的IO多路复用程序作为底层实现。
image.png
另外,IO多路复用程序这里主要监听AE_WRITABLE和AE_READABLE这两类事件:

  1. // ae.h
  2. /*
  3. * 文件事件状态
  4. */
  5. // 未设置
  6. #define AE_NONE 0
  7. // 可读
  8. #define AE_READABLE 1
  9. // 可写
  10. #define AE_WRITABLE 2
  11. /*
  12. * 时间处理器的执行 flags
  13. */
  14. // 文件事件
  15. #define AE_FILE_EVENTS 1
  16. // 时间事件
  17. #define AE_TIME_EVENTS 2
  18. // 所有事件
  19. #define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS)
  20. // 不阻塞,也不进行等待
  21. #define AE_DONT_WAIT 4

当Socket变得可读时或者有新的可应答Socket产生时,Socket会产生AE_READABLE事件;当Socket变得可写时会产生AE_WRITABLE事件。当然这两个事件在一个Socket上可能会同时产生,此时Redis服务器会优先处理读Socket,然后处理写Socket。


2. 文件事件处理器

文件事件是Redis中主要处理的事件类型,因此,Redis针对于不同的文件编写了不同的文件事件处理器,例如:

  • 连接应答处理器:用于对连接服务器的各个客户端进行应答
  • 命令请求处理器:用于接收客户端传来的命令请求
  • 回复处理器:用于向客户端返回命令的执行结果
  • 复制处理器:用于处理主从之间的复制操作

连接应答处理器

它主要用于对连接服务器监听Socket的客户端进行应答,相关函数定义如下:

  1. void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
  2. int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
  3. char cip[REDIS_IP_STR_LEN];
  4. REDIS_NOTUSED(el);
  5. REDIS_NOTUSED(mask);
  6. REDIS_NOTUSED(privdata);
  7. while(max--) {
  8. // accept 客户端连接
  9. cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
  10. if (cfd == ANET_ERR) {
  11. if (errno != EWOULDBLOCK)
  12. redisLog(REDIS_WARNING,
  13. "Accepting client connection: %s", server.neterr);
  14. return;
  15. }
  16. redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
  17. // 为客户端创建客户端状态(redisClient)
  18. acceptCommonHandler(cfd,0);
  19. }
  20. }

当Redis服务器初始化时,它会将连接应答处理器和监听的Socket的AE_READABLE事件关联。当有用户连接到服务器时,连接的Socket就会产生AE_READABLE事件,引发连接应答服务器执行响应的操作。
image.png

命令请求处理器
**
当客户端通过上一步和Redis服务器建立连接之后,服务器就会将客户端Socket的AE_READABLE事件和命令请求处理器相关联。当客户端后续发送来命令时,命令请求处理器就会读取到Socket中的内容,执行响应的操作。
image.png

命令回复处理器
**
既然有命令请求,那么必然就有命令回复处理器,它用于将命令执行后的结果通过Socket返回给客户端。当服务器有结果需要回复给客户端时,命令回复处理器就会关联Socket的AE_WRITABLE事件。当客户端准备好接收命令回复时,就会产生AE_WRITABLE事件,引发处理器执行相应的操作,将结果回复给客户端。
image.png
当回复处理器将命令回复全部写入到Socket之后,服务器就会解除Socket的AE_WRITABLE事件和命令回复处理器之间的关联。


3. 时间事件处理器

它主要用于处理Redis内部产生的一些时间事件,目前Redis中只存在周期性事件,它指每隔一段时间就会执行的事件。一个时间事件主要由如下三部分信息组成:

  • id:全局的唯一ID
  • when:毫秒级别的UNIX时间戳,记录事件到达的时间
  • timeProc:事件处理器

所有的时间事件都会放入一个无序链表中,每当时间事件执行器运行时,它就会遍历整个链表,查找所有已经到达的时间事件,最后调用相应的事件处理器。正常模式下,Redis只使用serverCron一个时间事件,最多也只会使用两个时间事件。所以,链表很短,事件执行的效率并不会受到链表长度的影响。

新事件采用头插法插入到链表头部,因此需要遍历整个链表。

serverCron事件用于对Redis服务器自身的资源和状态进行检查,确保服务器可以长期稳定的运行。例如:

  • 统计时间、内存、数据库等占用情况
  • 清理过期的键值对
  • 关闭清理无效连接
  • 尝试执行持久化操作
  • 主从定期同步
  • 集群模式下同步和测试集群健康情况
  • ……