- 文件事件(Redis线程模型)
- Reactor模型
- 四个组件
- sockets:READABLE和WRITABLE
- I/O多路复用:epoll,select,poll
- dispatcher
- handler:连接,接受命令,回复
- 运行机制
- 时间事件
- 总体执行流程
- 模型优势(高效原因)
- 参考:epoll, select, poll
先验知识:Reactor模型,I/O多路复用
-
Redis服务器是一个事件驱动程序,需要处理以下两类事件
-
文件事件
-
Redis服务器通过socket(事件)与客户端连接,服务器通过监听这些事件来完成网络通信操作
-
-
时间事件
-
Redis服务器中的一些操作(比如ServerCron函数)会定时执行
-
-
1.1 文件事件
Redis的线程模型,基于Reactor模式,称为文件事件处理器(File Event Handler)
- Reactor模式
- Reactor模型有三种:单线程Reactor模型,多线程Reactor模型,主从Reactor模型
- 主从Reactor模型是最健壮的,Tomcat和Netty都是使用这种;但是 Redis是使用单线程Reactor模型
- 构成:四个部分
- 套接字
- 概述:文件事件就是对socket操作的抽象, 每当一个 socket 准备好执行连接accept、read、write、close等操作时, 就会产生一个文件事件。一个服务器通常会连接多个socket, 多个socket可能并发产生不同操作,每个操作对应不同文件事件
- I/O多路复用
- 作用:监听多个套接字,并串行发向队列给dispatcher
- 实现:
- 通过包装底层的I/O多路复用函数:select,epoll,evport,kqueue
- 因为Redis为每个I/O多路复用函数实现了相同的API,因此会根据系统性能选择最优的函数
- 分派器dispatcher
- 根据套接字产生的事件类型,分派给对应的handler
- 事件处理器handler:主要有三个
- 连接答应处理器:客户端连接服务端
- 服务端初始化时绑定READABLE事件
- 命令请求处理器:客户端向服务端发命令
- 服务端初始化完成后,绑定READABLE事件,服务端开始读取客户端发来的命令
- 在客户端连接服务器的整个过程中,一直都绑定着
- 命令回复处理器:服务端向客户端返回执行结果
- 服务端有命令回复需要送回客户端时,绑定WRITABLE事件,写入客户端
- (不重要)复制处理器:用于复制操作
- 连接答应处理器:客户端连接服务端
- 套接字
- 事件的类型:READABLE和WRITABLE
- 当socket可读(比如客户端对Redis执行write/close操作),或有新的可应答的socket出现时(即客户端对Redis执行connect操作),socket就会产生一个AE_READABLE事件
- 当socket可写时(比如客户端对Redis执行read操作),socket会产生一个AE_WRITABLE事件
- I/O多路复用程序可以同时监听AE_REABLE和AE_WRITABLE两种事件,要是一个socket同时产生这两种事件,那么文件事件分派器优先处理AE_REABLE事件。即一个socket又可读又可写时, Redis服务器先读后写socket
- 运行机制
- 1. 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,即将套接字的fd注册到epoll上,当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,与操作相对应的文件事件就会产生。
- 2. 尽管多个文件事件可能会并发地出现,但I/O多路复用程序总是会将所有产生事件的套接字都推到一个队列里面,然后通过这个队列,以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字。
- 3. 此时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。文件事件处理器以单线程方式运行,这就是之前一直提到的Redis线程模型中,效率很高的那个单线程
1.2 时间事件
- 时间事件主要分两类
- 定时事件:只执行一次,比如在30秒后执行
- 周期性事件:每隔n毫秒执行一次
- 内部原理:将时间事件放在一个无序链表中,有三个参数
- id:事件ID,从小到大递增
- when:下次执行时间
- timeProc:需要调用的handler
- 周期性事件:ServerCron函数,用于确保redis服务可以长期稳定运行
- 默认每秒10次,即100ms一次。2.8后可根据conf文件配置
- 主要工作包括(详细见13,14章)
- 更新服务器的各种统计信息,比如UNIX时间,内存占用,数据库占用
- 清理过期的KV对(定期清理)
- 关闭和清理失效的client
- 尝试进行AOF / RDB 持久化
- 如果是主服务器,那么对从服务器进行定期同步
- 集群模式下,对集群进行定期同步和连接测试
1.3 总体执行流程
- 主函数
-
def main(): //初始化服务器 init_server() //一直轮询,直到服务器关闭为止 while server_is_not_shutdown(): aeProcessEvents() //服务器关闭,执行清理操作 clean_server()
-
- 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()
-
2. 模型优势 / 高效原因
- 纯内存访问
- 非阻塞I/O:Redis采用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间
- 单线程执行:没有上下文切换
Ref
https://segmentfault.com/a/1190000037434936
更详细的内容:https://louyuting.blog.csdn.net/article/details/112281453
深入剖析 redis 事件驱动:https://www.cnblogs.com/daoluanxiaozi/p/3590093.html