参考:<<Redis设计与实现>>
- 注:这本书是基于Redis3.0版本写的,和后面的版本有点差异
一、时间事件
Redis的时间事件分为两类:
- 定时事件:让一段程序在指定的时间之后执行一次。
- 周期性事件:让一段程序每隔指定时间就执行一次。
一个时间事件主要由以下三个属性组成:
- id:服务器为时间事件创建的全局唯一ID(标识号)。ID号按从小到大的顺序递增。
- when:毫秒精度的UNIX时间戳。记录了时间事件到达时间。
- timeProc:时间处理器,一个函数。当时间事件到达时,服务器就会调用相应的处理器来处理事件。
事件是定时事件还是周期性事件取决于时间事件处理器的返回值:
- 返回ae.h/AE_NOMORE(-1),则为定时事件:该事件在到达一次之后就会被删除,之后不再到达
- 返回非AE_NOMORE(-1)的整数值,则为周期性事件:当一个时间事件到达之后,服务器会根据时间处理器返回的值对时间事件的when属性进行更新,让这个事件在一段时间后再次到达,并以这种方式一直更新运行。
ae.h源码:https://github.com/antirez/redis/blob/3.0/src/ae.h
目前版本的Redis只使用周期性事件,没有使用时间事件。
1.1 实现
服务器将所有时间都放在一个无序列表中,每当时间事件执行器运行时,它遍历整个链表查找所有已到达的时间事件,并调用相应的时间处理器。
无序指的是不按照when属性值进行排序,由于新创建的时间事件是插入到链表的表头,所以链表中时间事件是按照id属性降序排序的。
一个保存了三个时间事件链表的例子:
注:
- 由于没有按照执行时间进行排序,所以时间事件执行器运行时,它必须遍历链表中的所有时间事件,这样才能确保服务器中所有已到达的时间事件都会被处理。
- 在当前版本中,正常模式下只有一个serverCron时间事件,在benchmark模式下也只是使用了两个时间事件。这种情况下,使用无序链表并不影响事件的执行性能。
1.2 涉及的函数(ae.c源码中)
- aeCreateTimeEvent():添加新的时间事件至服务器,接收milliseconds(毫秒数)和proc(处理器)等参数。新的事件将在当前时间的milliseconds毫秒后到达,事件处理器是proc。
- aeDeleteFileEvent():从服务器删除指定ID对应的时间事件
- aeSearchNearestTimer():返回到达时间距离当前时间最近的时间事件。
- processTimeEvents():时间事件的执行器。这个函数会遍历所有时间事件,并调用事件处理器来处理那些已到达的时间事件。
已经到达:时间事件的when属性记录的UNIX时间戳小于等于当前时间。
1.3 时间事件的应用实例:serverCron函数
持续运行的Redis服务器需要定期对自身的资源和状态进行检查和调整,确保服务器可以长期、稳定地运行。这些操作由redis.c/serverCron函数负责执行,该函数主要工作如下:
- 更新服务器的各类统计信息,如时间、内存占用、数据库占用情况等
- 清理数据库中的过期键值对
- 关闭和清理连接失效的客户端
- 尝试进行AOF或RDB持久化操作
- 如果服务器是主服务器,那么对从服务器进行定期同步
- 如果处于集群模式,对集群进行定期执行同步和连接测试
Redis服务器一周期性事件方式运行该函数,直到服务器关闭。
在redis.conf中可以配置hz选项调整serverCron函数每秒执行次数
- 默认配置值是10,取值范围是1-500。
- 官方不建议将值设置大于等于100,因为当redis空闲时会占用更多CPU。除非在某些环境中,你需要一个非常低的延迟,可以将它设置为100
二、事件的调度与执行
服务器中同时存在文件事件和时间事件,所以服务器需要对这两种事件进行调度,决定何时该处理文件事件、何时又该处理时间事件,以及花多长时间执行等。
事件调度与执行流程如下:
- 根据最近的时间事件判断是否阻塞并等待文件事件产生,如果时间事件已经到达或有文件事件产生,那么不阻塞;否则阻塞,阻塞最大时间由时间事件距离当前时间决定
- 处理所有已经产生的文件事件
- 处理所有已经到达的时间事件
- 开始新的循环
上述流程如下图所示:
注:
- 最大阻塞时间由最近的时间事件决定,这样可以避免服务器对时间事件进行频繁轮询,也可以确保阻塞过长时间
- 文件事件是随机出现的,如果等待处理完一次文件事件后仍没有时间事件到达,那么服务器会再次等待处理文件事件。直到有时间事件,服务器再处理。
- 对文件事件、时间事件都是同步、有序、原子地执行的,服务器不会中途中断、抢占。因此,不管是文件事件还是时间事件的处理,都会尽可能的减少程序的阻塞时间,在有需要的时候让出执行权,降低事件饥饿的可能性。
- 如在命令回复处理器将一个命令回复写入到客户端套接字时,如果写入字节数超过了一个预设常量,命令回复处理器就会主动跳出写入循环,将余下的数据下次再写
- 时间事件会将非常耗时的持久化操作放到子线程或子进程中执行
- 时间事件在文件事件后执行,由于事件之间不会出现抢占,所以时间事件的实际处理事件通常会比设定的时间晚一点