《Redis设计与实现》读书笔记(十七) ——Redis时间事件与事件调度
(原创内容,转载请注明来源,谢谢)
一、时间事件
1、概述
redis的时间事件分为两类,一类是定时事件,在某个时刻执行;另一类是周期性事件,每隔一段时间执行一次。
时间事件由三部分组成——全局id,标识事件,新的事件比旧的事件id大;unix毫秒级时间戳,记录时间事件的到达时间;事件处理器,时间事件到达时调用相应的处理器进行处理。
一个时间事件是定时还是周期性,取决于其返回值:如果返回的是AE_NOMORE,表示其是一次性的事件,即定时的,执行完毕后redis会将其删除;如果返回的不是该结果,则表示是周期性事件,服务器会根据返回值,更新unix毫秒级时间戳,这样当下一次时间到达时又会执行该事件。
2、实现
redis将所有的时间事件放在一个无序链表中,当时间事件执行器执行时,会遍历整个链表,将所有已到达执行时间的事件调用相应的时间事件处理器进行处理。
虽然是无序链表,但是由于新的时间事件总是插入到表头,因此表头总是最新的时间事件。
无序链表结构如下图:
采用无序链表,是指链表的元素不按照when的先后进行排序,而是强制遍历整个链表,确保所有到达执行时间的事件都能被执行。
在目前的情况下,redis只使用serverCron函数作为时间事件,相当于仅有一个时间事件,因此这个链表可以简化为一个指针。
流程如下:
1)遍历服务器中所有的时间事件。
2)检查每个事件是否到达执行时间,对于已经到达的,执行事件,并获取返回值。
3)如果返回的是AE_NOMORE,表示是定时事件,则服务器删除该事件;否则表示其是周期性事件,服务器会更新事件的when属性,标记下一次执行时间。
3、serverCron函数
redis目前仅使用serverCron函数作为时间事件,即目前仅有这一个时间事件。该时间事件主要进行以下操作:
1)更新redis服务器各类统计信息,包括时间、内存占用、数据库占用等情况。
2)清理数据库中的过期键值对。
3)关闭和清理连接失败的客户端。
4)尝试进行aof和rdb持久化操作。
5)如果服务器是主服务器,会定期将数据向从服务器做同步操作。
6)如果处于集群模式,对集群定期进行同步与连接测试操作。
redis服务器开启后,就会周期性执行此函数,直到redis服务器关闭为止。默认每秒执行10次,平均100毫秒执行一次,可以在redis配置文件的hz选项,调整该函数每秒执行的次数。
二、事件调度与执行
由于redis服务器同时存在文件事件和时间事件,因此必须对这两个事件进行调度,决定何时处理文件事件,何时处理时间事件,以及花费多少时间处理这两类事件。
1、事件调度执行流程
事件的调度和执行由ae.c/aeProcessEvents函数负责,执行流程如下:
1)启动服务器,初始化服务器,一直处理事件,循环下面的2~6步骤,直到服务器关闭。服务器关闭会执行相关的清理操作。
2)获取到达时间距离当前时间最近的时间事件,计算到达时间,单位是毫秒,假设结果是x毫秒。
3)如果时间已经到达,则x是负数,将x置成0。
4)如果x不是0,则程序阻塞,等待文件事件的产生,再进入下一步,其中程序阻塞的最大等待时间是x毫秒,因为即使x毫秒内都没有文件事件的产生,但是x毫秒后必然有时间事件需要执行,因此不能继续阻塞;如果x是0,即已经有时间事件到了需要执行的时候,则程序不阻塞,直接进入下一步。
5)处理所有已经产生的文件事件。
6)处理所有已经产生的时间事件。
2、事件调度执行规则
1)程序等待文件事件的最大阻塞时间,是由到达时间最接近当前时间的时间时间决定,即避免了程序对时间事件的不断轮询,又保证阻塞时间不会太长。
2)由于文件事件的发生,是由客户端决定,即完全随机的。因此程序处理完一个文件事件后,如果没有新的待处理的文件事件,且还没到达最近的时间事件的执行时间,则程序会继续阻塞,直到达到最近的时间事件的时间,或期间有新的文件事件。
3)文件事件和时间事件都是同步、有序、原子的执行,执行一个事件的时候,其他事件会阻塞等待,不会发生事件的抢占。两类事件处理器都会减少程序的阻塞时间,并在有需要的时候主动让出执行权,避免事件的饥饿等待。
例如:文件事件的命令回复处理器,如果内容太多,写入的字节数超出预设的常量,则处理器会自动break,留下剩余的内容下一次再写;时间事件中会将耗时的rdb持久化、aof重写等操作,通过创建子进程,由子进程执行。
4)由于都是优先处理文件事件,后处理时间事件,且处理过程不会发生事件抢占,因此时间事件的实际执行时间,有可能比设定的执行时间稍晚一些。
执行示例如下图:
在等待下一个时间事件的过程中,程序处理了两个文件事件。其中第85毫秒,由于还没到时间事件的执行时间,而有文件事件,因此处理文件事件。由于文件事件处理完毕后是在130毫秒,则时间事件只能在131毫秒执行,比预设的100毫秒晚了31毫秒。
三、总结
1、redis服务器是事件驱动程序,事件分为文件事件和时间事件。
2、文件事件处理程序是基于Reactor的网络通信程序,是对套接字操作的抽象,每当套接字变成可应答、可读、可写的时候,相应的文件事件就会产生。
3、文件事件分为读事件(AE_READABLE)和写事件(AE_WRITEABLE)两类。
4、时间事件分为定时事件和周期性事件,当前redis只有周期性事件,且周期性事件中只有一个事件——serverCron函数,该函数默认每秒执行10次,可以通过配置文件修改每秒执行次数。
5、文件事件和时间事件是合作关系,服务器会轮流处理两个事件,事件不会发生抢占。由于通常先处理文件事件,因此时间事件的实际执行时间有可能比预设时间稍晚。
——written by linhxx 2017.09.06