时间事件
事件的调度与执行
因为服务器中同时存在文件事件和时间事件两种事件类型,所以服务器必须对这两种事件进行调度,决定何时应该处理文件事件,何时有应该处理时间事件,以及花多少事件来处理它们等等。事件的调度和执行由ae.c/aeProcessEvents函数负责,伪代码表示如下:
def aeProcessEvents():
# 获取到达时间离当前时间最接近的时间事件
time_event = aeSearchNearestTimer()
# 计算最接近的时间事件距离到达还有多少毫秒
remaind_ms = time_event.when - unix_ts_now()
# 如果事件已到达,那么remaind_ms可能为负数,将它设定为0
if remaind_ms < 0:
remaind_ms = 0
# 根据remaind_ms的值,创建timeval结构
timeval = create_timeval_with_ms(remaind_ms)
# 阻塞并等待文件事件产生,最大阻塞事件由传入的timeval结构决定
# 如果remaind_ms的值为0,那么aeApiPoll调用之后马上返回,不阻塞
aeApiPoll(timeval)
# 处理所有易产生的文件事件
processFileEvents()
# 处理所有已到达的时间事件
processTimeEvents()
将aeProcessEvents函数置于一个循环里面,加上初始化和清理函数,这就构成了Redis服务器的主函数,以下是该函数的伪代码表示:
def main():
# 初始化服务器
init_server()
# 一直处理事件,直到服务器关闭为止
while server_is_not_shutdown():
aeProcessEvents()
# 服务器关闭,执行清理操作
clean_server()
从事件处理的角度来看,Redis服务器的运行流程可以用流程图来表示
事件的调度和执行规则:
- 1.aeApiPoll函数的最大阻塞事件由到达时间最接近当前时间的时间事件决定,这个方法既可以避免服务器对时间事件进行频繁的轮询(忙等待),也可以确保aeApiPoll函数不会阻塞过长时间
- 2.因为文件事件是随机出现的,如果等待并处理完一次文件事件之后,仍未有任何时间事件到达,那么服务器将再次等待并处理文件事件。随着文件事件的不断执行,时间会逐渐向时间事件所设置的到达时间逼近,并最终来到到达时间,这时服务器就可以开始处理到达的时间事件了
- 3.对文件事件和时间事件的处理都是同步的、有序、原子地执行的,服务器不会中途中断事件处理,也不会对事件进行抢占,因此,不管是文件事件的处理器,还是时间事件的处理器,它们都会尽可能地
减少程序地阻塞时间,并在有需要时主动让出执行权,从而降低造成事件饥饿地可能性。比如说,在命令回复处理器将一个命令回复写入到客户端套接字时,如果写入字节数超过了一个预设常量的话,命令回复
处理器就会主动用break跳出写入循环,将余下的数据留到下次再写;另外时间事件也会将非常耗时的持久化操作放到子线程或者子进程执行 - 4.因为时间事件在文件事件之后执行,并且事件之间不会出现抢占,所以时间事件的实际处理事件,通常回避时间事件设定的到达时间晚一些
例子
举个例子,事件执行过程凸显了上面的规则,
- 1.因为时间事件尚未到达,所以在处理时间事件之前,服务器已经等待并处理了两次文件事件
- 2.因为处理事件的过程中不会出现抢占,所以实际处理时间事件的时间比预定的100毫秒慢了30毫秒
重点
- 1.Redis服务器是一个事件驱动程序,服务器处理的事件分为时间事件和文件事件两类。
- 2.文件事件处理器是基于Reactor模式实现的网络通信程序
- 3.文件事件是对套接字操作的抽象:每次套接字变得可应答(acceptable)、可写(writable)或者可读(readale)时,相应的文件事件就会产生
- 4.文件事件分为AE_READABLE事件(读事件)和AE_WRITABLE事件(写事件)两类
- 5.时间事件分为定时事件和周期性事件:定时事件只在指定的事件到达一次,而周期性事件则每隔一段时间到达一次
- 6.服务器在一般情况下只执行serverCron函数一个时间事件,并且这个事件是周期性事件
- 7.文件事件和时间事件之间是合作关系,服务器会轮流处理这两种事件,并且处理事件的流程中也不会出现抢占
- 8.时间事件的实际处理时间通常回避设定的到达时间要晚一些