- 前言:Redis服务端是一个事件驱动程序,需要处理的事件分为两种:
- 通过套接字和客户端的通讯称为
文件事件
- 自身的定时任务,前面提到过的serverCorn函数,称为
时间事件
。
- 通过套接字和客户端的通讯称为
12.1 文件事件
- Redis基于Reactor模式(https://www.jianshu.com/p/764067af8039)开发了自己的网络事件处理器,称为
文件事件处理器
。- I/O多路复用监听多个套接字,并关联不同的
事件处理器
,事件处理器
就是一个个函数,Reactor模式文章中也提到了 - 套接字
准备好执行
连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,相关事件
就会产生,然后执行相应事件处理器
- I/O多路复用监听多个套接字,并关联不同的
- 事件处理器以单线程执行,但因为Reactor模式所以可以同时监听多个套接字。
12.1.1 文件事件处理器构成
redis文件事件处理器.png- 上图和https://www.jianshu.com/p/764067af8039 中单线程reactor类似,
I/O多路复用程序
查询就绪事件放入队列,每次向文件事件分派器
发送一个套接字,文件事件分派器
调用事件处理器
,执行结束后再发送下个套接字,直到单次队列就绪事件执行完毕,再次查询就绪事件一直循环,所以整体上说Redis是单线程执行的。
12.1.2 I/O多路复用程序实现
- I/O多路复用功能通过包装select、epoll、evport、kqueue函数库实现,这些函数都是由系统内核自带的,Redis自动选择最佳的实现方式。
12.1.3 事件的类型
- I/O多路复用可以监听多个套接字的
ae.h/AE_READABLE
事件和ae.h/AE_WRITEABLE
事件:- 套接字可读时,客户端write操作、客户端close操作、接收新客户端可触发
AE_READABLE
- 套接字可写时,客户端read操作触发
ae.h/AE_WRITEABLE
- 同时有两种事件,优先处理
ae.h/AE_READABLE
- 套接字可读时,客户端write操作、客户端close操作、接收新客户端可触发
12.1.4 API
-
ae.c/aeCreateFileEvent
:把套接字和指定事件加入到I/O多路复用程序监听,并关联事件和事件处理器。 -
ae.c/aeDeleteFileEvent
:取消监听套接字的事件,并且取消事件和事件处理器关联。 -
ae.c/aeGetFileEvents
:返回套接字的监听事件类型 -
ae.c/aeWait
:传入套接字、事件和等待时间,给定时间内阻塞等待这个套接字的事件,事件发生或者超时后返回。 -
ae.c/aeApiPoll
:指定时间内阻塞等待所有aeCreateFileEvent监听的套接字事件,产生至少一个或者超时返回。 -
ae.c/aeProcessEvents
:这个函数就是文件事件分派器
,调用aeApiPoll返回结果后串行调用事件处理器
-
ae.c/aeGetApiName
:返回I/O多路复用的具体内核实现,如上面提到的的select、epoll、kqueue等等
12.1.5 文件事件处理器
- Redis为文件事件编写了多个处理器,例如:
- 服务端对连接的客户端应答时的
连接应答处理器
- 接收客户端请求时的
命令请求处理器
- 返回客户端结果时的
命令回复处理器
- 主从服务器复制时的
复制处理器
- 服务端对连接的客户端应答时的
- 连接应答处理器:
-
networking.c/acceptTcpHandler
:用于对连接服务端的客户端应答的handler,服务器初始化时会关联AE_READABLE
事件,当客户端用sys/socket.h/connect
函数连接服务端时,会产生AE_READABLE
事件
acceptTcpHandler.png
-
- 命令请求处理器
-
networking.c/readQueryFromClient
:当客户端通过应答处理器接入成功后,服务端将客户端套接字和AE_READABLE事件和命令请求处理器
关联,客户端发送请求时,会触发AE_READABLE事件,进而执行这个处理器。
readQueryFromClient.png
-
- 命令回复处理器
-
networking.c/sendReplyToClient
:当服务端端有命令回复需要传送给客户端的时候,服务端将客户端套接字和AE_WRITEABLE事件和命令回复处理器
关联,客户端准备好接收回复命令时触发AE_WRITEABLE事件,回复完成后解除事件和处理器关联。
sendReplyToClient.png
-
-
一次完整的客户端与服务器连接事件示例:
示例.png
12.2 时间事件
- Redis时间事件分为两类(和文件事件一样都是函数):
- 定时事件:指定时间后执行一次。
- 周期性事件:每隔指定时间指定时间执行一次。
- 时间事件由三部分组成:
- id:从小到大排序,越大的事件越新
- when:UNIX时间戳,记录事件到达的时间
- timeProc:时间事件处理器,一个函数,到达时间后调用
- 一个时间事件是定时事件还是周期性事件取决于
函数返回值
:- 返回
ae.h/AE_NOMORE
:定时事件,执行一次后删除。 - 返回非
ae.h/AE_NOMORE
整数:周期性事件,这个整数是下次再执行的间隔时间,这时把事件的when属性更新,到达指定时间就可以再次执行,以此类推形成周期性事件。 - 书中版本Redis3.0暂时没有使用定时事件
- 返回
12.2.1 实现
- 服务器把所有时间事件放在无序链表里,执行时遍历到达执行时间的事件,调用处理器。
正常模式下Redis只有serverCron一个时间事件
12.2.2 API
- 创建:
ae.c/aeCreateTimeEvent
,接收一个毫秒数和事件处理器,到达时间后执行这个处理器。 - 删除:
ae.c/aeDeleteFileEvent
,接收一个事件id - 查询:
ae.c/aeSearchNearestTimer
,查询一个最近的即将到达的事件 - 遍历:
ae.c/processTimeEvents
,遍历所有事件,调用所有到达执行时间的事件处理器。
12.2.3 时间事件应用实例:
- serverCron函数主要工作:
- 更新统计信息,比如事件、内存占用,数据库占用情况等
- 清理过期键值对
- 关闭失效客户端
- 尝试进行AOF或RDB持久化操作
- 如果是主服务器,对从服务器的同步工作
- 如果是集群模式定期同步和连接测试
- serverCron函数默认每100ms执行一次,可配置
12.3 事件的调度与执行
- 服务器同时存在时间事件和文件事件,需要决定什么时候处理时间事件,什么时候处理文件事件,处理多长时间等等,函数是
ae.c/aeProcessEvents
,主要流程就是aeSearchNearestTimer
获取最近要发生的一个事件,如果时间已经到了就执行,否则就等待,先处理文件事件,再处理时间事件 - Redis服务器主函数的构成就是
初始化
、无限循环aeProcessEvents
、关闭执行清理
服务器运行流程.png - 调度和执行规则:
- 因为取离现在最近一次发生的事件并等待,避免了频繁的轮询
- 文件事件和时间事件都是同步、有序、原子的,不会被中断和抢占,所以处理器内部有相应的任务拆分优化,或者用子线程、子进程执行,避免长时间执行导致后续处理器阻塞。
总结
- Redis的事件分为文件事件和时间事件。
- 文件事件就是Redis对reactor模式具体实现,以便单线程处理多客户端。
- 时间事件就是定时函数和周期函数,时间事件主要关注
serverCron
函数。 - 这两种事件包含了Redis服务端的主要工作,流程如上图“服务器运行流程”。
网友评论