12.1 文件事件
文件事件分为写事件(AE_WRITABLE)和读事件(AE_READABLE)
采用I/O复用程序监听多个套接字,根据套接字当前执行的任务关联事件处理器。当一个套接字准备好执行应答、读取、写入、关闭操作时,会产生对应的文件事件,文件事件分派器会将文件事件交给对应的事件处理器。
同一时刻可能有多个套接字准备完成,I/O复用程序将这些套接字放到缓冲队列中,每次只处理一个套接字。
12.1.5 文件事件的处理器
Redis为文件处理器关联了多个处理器,比如
- 回应客户端的连接请求,为监听套接字的AE_READABLE事件关联连接应答处理器
- 接收客户端的请求命令,为客户端套接字的AE_READABLE事件关联命令请求处理器
- 向客户端发送响应,为客户端套接字的AE_WRITABLE事件关联命令回复处理器
一次完整的客户端与服务器连接事件示例:
1. 服务器的监听套接字的AE_READABLE事件处于监听状态下,事件关联的应答器为连接应答处理器。
2. 一个客户端A向服务器发起连接请求,监听套接字产生AE_READABLE事件,触发连接应答处理器执行
3. 连接应答处理器为该客户端创建客户端套接字以及客户端状态,并将客户端套接字的AE_READABLE事件关联到命令请求处理器,使客户端可以向服务器发送命令请求
4. 客户端A向服务器发送请求命令,客户端套接字创建AE_READABLE事件,触发命令请求处理器执行,读取客户端命令内容,传给响应程序执行
5. 执行完毕回传命令回复,服务器将客户端套接字的AE_WRITABLE事件关联到命令回复处理器,当客户端A尝试读取命令回复时,客户端套接字产生AE_WRITABLE事件,触发命令回复处理器执行
6. 命令回复处理器将回复内容全部写入到客户端套接字,服务器将客户端套接字的AE_WRITABLE事件解除与命令回复处理器的关联
12.2 时间事件
Redis的时间事件分为两类:定时事件和周期性事件
一个时间事件有如下三个属性:
- id:全局唯一,递增
- when:毫秒精度时间戳,记录了时间事件的到达时间
- timeProc:时间事件处理器,一个函数,当事件到达时,调用处理器处理
如果时间事件处理器返回是ae.h/AE_NOMORE,该事件定义为定时事件,该事件到达一次后被删除,不会再次到达。如果返回非ae.h/AE_NOMORE的整数值A,以A更新when属性,服务器让事件在A毫秒后再次到达。目前Redis只使用周期性事件。
12.2.1 实现
服务器将所有的时间事件放在一个无序链表(没有按照when属性排序)中,当时间事件执行器运行时,遍历链表,查找已到达的时间事件,调用相应的事件处理器。链表遍历不影响性能,正常模式的Redis服务器只使用一个或者两个时间事件。
12.2.3 时间事件应用实例:serverCron函数
为了保证Redis长期运行的稳定性,需要定期对自身资源和状态进行检查,定期操作由redis.c/serverCron函数负责执行,其主要工作包括
- 更新服务器的各类统计信息,比如时间、内存占用、数据库占用情况
- 清理数据库中的过期键值对
- 关闭和清理连接失效的客户端
- 尝试进行AOF或RDB持久化操作
- 主服务器定期对从服务器进行同步
- 集群模式下,对集群定期进行同步和测试
在Redis2.6版本中,默认每秒运行10次,在Redis2.8开始,可以通过选项配置。
12.3 事件的调度和执行
协调时间和文件事件的处理。
启动服务器后,进入循环:
- 循环开始判断当前是否需要关闭服务器
- 计算最近即将到达的时间事件的等待时间,在这段时间里阻塞并等待文件事件,当文件事件到达后处理文件事件,并继续等待文件事件,直到阻塞时间到来处理时间事件
对文件事件和时间事件的处理是同步、有序、原子地执行,不存在抢占,服务器也不会中断某个正在执行的事件转而处理其他事件。当处理事件过长时,事件对应的命令执行器会主动让出执行权,降低事件饥饿的可能性。时间事件会将非常耗时的处理放到子线程或者子进程中处理。
因为时间事件在文件事件之后执行,所以执行时间事件时,会比预定时间稍晚。