Redis服务器是一个事件驱动程序,需要处理以下两类事件:
- 文件事件(file event):Redis服务器通过socket与客户端连接,文件事件就是对套接字操作的对象。服务器与客户端的通信会产生相应的文件事件,服务器监听并处理这些事件来完成一系列的网络通信操作。
- 时间事件(time event):Redis服务器的一些操作(如
serverCron
函数)需要在特定时间点执行,时间事件就是对这类定时任务的抽象。
Redis基于Reactor模式开发了自己的网络事件处理器,称为『文件事件处理器』,文件事件处理器以单线程方式运行。
文件事件处理器的四个组成部分:
-
套接字。
当被监听的套接字准备好执行accept、read、write、close等操作时,与操作相对应的文件事件就会产生。
-
I/O多路复用程序。
使用I/O多路复用程序同时监听多个套接字,并向文件分派器传送那些产生了事件的套接字(使用队列)。
-
文件事件分派器
根据套接字的事件类型,调用相应的事件处理器。
-
事件处理器
Redis的I/O多路复用包装了常见的select、poll、evport和kqueue等函数库来实现的,每个函数库的在Redis源码中都有一个独立的文件。
I/O多路复用程序可以监听多个套接字的ae.h/AE_READABLE和ae.h/AE_WRITABLE事件。两种事件可以同时监听,但会优先处理AE_READABLE事件。
函数 | 参数 | 作用 |
---|---|---|
ae.c/aeCreateFileEvent | 套接字描述符、事件类型、事件处理器 | 将给定套接字的给定事件加入到I/O多路复用程序的监听范围之内,并对事件和事件处理器进行关联 |
ae.c/aeDeleteFileEvent | 套接字描述符、监听事件类型 | 让I/O多路复用程序取消套接字的事件监听,并取消事件与事件处理器的关联 |
ae.c/aeGetFileEvent | 套接字描述符 | 返回套接字正在被监听的事件类型,none、readable、writable |
ae.c/aeWait | 套接字描述符、事件类型、毫秒数 | 在给定时间内阻塞并等待套接字的给定类型的事件发生 |
ae.c/aeApiPoll | timeval结构 | 在给定事件内,阻塞并等待所有被acCreateFileEvent函数设置为监听状态的套接字产生文件事件 |
ae.c/aeProcessEvents | 先调用aeApiPoll来等待事件,然后遍历所有事件,调用相应的事件处理器 | |
ae.c/aeGetApiName | 返回I/O多路复用程序底层使用的函数库名称:epoll、select等 |
Redis为文件事件编写了多个处理器,分别用于实现不同的网络通信需求:
-
连接应答处理器:监听客户端的套接字,并应答。
networking.c/acceptTcpHandler函数,具体实现为sys/socket.h/accept函数的包装。服务器初始化时,会将这个处理器与套接字的AE_READABLE事件关联起来。
-
命令请求处理器:接受来自客户端的命令请求。
networking.c/readQueryFromClient函数,具体实现为unistd.h/read函数的包装。
-
命令回复处理器:向客户端返回命令的执行结果。
networking.c/sendReplyToClient函数,具体实现为unistd.h/write函数的包装。
-
复制处理器:主从服务器的复制操作。
Redis的时间事件分为两类:
- 定时事件:在指定一段时间后执行一次。
- 周期性事件:每隔一段时间就执行一次。
时间事件主要有三个属性:
- id
- when:毫秒进度的UNIX时间戳,事件的到达时间。
- timeProc:时间事件处理器,事件到达时,负责处理事件。
一个事件是定时事件还是周期性事件,取决于时间事件处理器的返回值:
- 返回ae.h/AE_NOMORE就是定时事件,到达一次后就删除
- 返回非AE_NOMORE的整数值就是周期性事件,事件到达后,根据返回值对when属性进行更新。
服务器的所有时间事件存放在一个无序链表(不按when属性排序)中,每当时间事件处理器运行时,遍历整个链表,找到已到达的事件,调用相应的事件处理器。
函数 | 参数 | 作用 |
---|---|---|
ae.c/aeCreateTimeEvent | 毫秒数,时间事件处理器 | 将新的时间事件添加到服务器 |
ae.c/aeDeleteFileEvent | 事件ID | 删除时间事件 |
ae.c/aeSearchNearestTimer | 返回到达时间最近的事件 | |
ae.c/processTimeEvents | 时间事件的执行器,遍历并调用事件 |
serverCron函数的工作包括:
- 更新服务器的统计信息,如时间、内存占用、数据库占用
- 清理过期的键值对
- 关闭和清理失效的连接
- 尝试AOF或RDB持久化
- 如果是主服务器,对从服务器定期同步
- 如果是集群模式,对集群进行同步和测试连接
调度和执行由ae.c/aeProcessEvents函数负责。
def aeProcessEvents():
# 获取最近的事件
time_event = aeSearchNearestTimer()
# 计算最近的事件还有多少毫秒
remaind_ms = time_event.when - unix_ts_now()
# 如果事件已到达
if remaind_ms < 0:
remaind_ms = 0
# 根据remaind_ms的值,创建timeval结构
timeval = create_timeval_with_ms(remaind_ms)
# 阻塞并等待文件事件
# 如果remaind_ms为0,那么aeApiPoll调用之后马上返回,不阻塞
aeApiPoll(timeval)
# 处理所有已产生的文件事件
processFileEvents()
# 处理所有已到达的时间事件
processTimeEvents()
调度和执行的规则如下:
- aeApiPoll函数的最大阻塞时间由到达时间最接近当前时间的事件决定,避免服务器的频繁轮询。
- 如果处理完一次文件事件后,未有时间事件到达,则再次处理文件事件。
- 对事件的处理都是同步、有序、原子地执行。不会中断、抢占事件处理。
- 时间事件的处理时间,通常比其设定的到达时间晚一些。
上一章:11. AOF持久化
下一章:13. 客户端