Chapter 12 - 事件
Created by : Mr Dk.
2020 / 06 / 05 16:17
Nanjing, Jiangsu, China
Redis 服务器是事件驱动的,主要处理两类事件:
- 文件事件 (file event) - 服务器对 socket 的抽象
- 时间事件 (time event)
File Event
Redis 通过 I/O 多路复用 来监听多个 socket,当得到 socket 的命令时,与 socket 关联的 handler 就会被调用。由于 Redis 由单进程的方式被实现,所以 I/O 多路复用程序还是会将所有的 socket 放到一个队列中,然后通过这个队列有序、同步地依次处理事件。每个 handler 中定义了对应事件发生时服务器应该执行的动作:
- accept
- read
- write
- close
I/O 多路复用程序的底层有:
- select
- epoll
- evport
- kqueue
Redis 在编译时会自动选择系统中性能最佳的多路复用库。
Handler 的具体类型:
- 连接应答处理器 - 封装了
accept()
函数,对 socket 进行应答 - 命令请求处理器 - 封装了
read()
,从客户端读入命令请求 - 命令回复处理器 - 封装了
write()
,将命令回复发送给客户端
Time Event
Redis 将所有的时间事件都放在一个无序链表中。每当时间事件执行器运行时,就遍历整个链表,对每个已超时的事件调用 handler。
Redis 中的 serverCron()
函数就是时间事件的例子。在这个事件的 handler 中有如下工作:
- 更新服务器的统计信息
- 清理过期的 key
- 关闭和清理连接失效的客户端
- AOF/RDB 持久化
- 如果服务器是主服务器,则对从服务器进行定期同步
- 如果处于集群模式,则对集群进行定期同步和连接测试
这个函数默认每秒运行 10 次,也可以通过配置文件进行调整。
Scheduler
在 Redis 主进程的主循环中,首先计算最近的时间事件还有多久发生。然后在最近的时间事件发生之前,阻塞等待文件事件 (因为文件事件是随机发生的),最久阻塞到下一个时间事件发生前。然后先处理文件事件 (本轮循环中可能没有),再处理时间事件 (本轮循环中也可能没有);最终重新循环。
对事件的处理都是同步、有序、原子地进行的,不存在中断与抢占。因此 handler 应当尽可能减少阻塞时间,并在有必要时让出 CPU:
- 比如,对 socket 进行
write()
时,写够一定的字节数后,余下的字节下次再写 - 将持久化操作放到子线程或子进程中
这里的原则与 Vert.x 类似,不要阻塞 event loop。