Redis 服务器是一个事件驱动程序,需要处理以下两类事件:
Redis 基于 Reactor 模式开发自己的网络时间处理器:文件事件处理器
使用 I / O 多路复用程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器
当套接字准备好执行连接应答、读取、写入、关闭等操作时,与之对应的文件事件产生,文件处理器调用套接字之前关联的事件处理器来处理
虽然文件处理器是单线程,但通过 I / O 多路复用监听多个套接字,可以实现了高性能的网络通信模型,又能很好与服务器中单线程的模块进行对接,保证了 Redis 内部单线程的简单性
服务器通常连接多个套接字,多个文件事件可能并发地出现,但 I / O 复用总是将所有产生事件的套接字放到一个队列中,有序、同步、每次一个套接字向文件事件分派器传送套接字,只有上一个套接字产生的事件处理完毕(即该套接字为事件所关联的事件处理器执行完毕),才会向文件事件分派器传送下一个套接字
所有功能都是通过包装常见的 select、epoll、evport、kqueue 等多路复用函数库,在 Redis 中对应 ae_select.c等
Redis 为每个多路复用函数库都实现了相同的 API,I / O 多路复用程序底层实现是可以互换的
I / O 多路复用程序可以监听多个套接字的 ae.h/AE_READABLE 事件和 ae.h/WRITEABLE 事件
如果一个套接字同时产生这两种事件,文件分派器会优先处理 AE_READABLE 事件
即如果一个套接字又可读又可写,则服务器先读再写套接字
ae.c
aeCreateFileEvent:套接字描述符、事件类型、事件处理器作为参数,将给定套接字的给定事件加入到 I / O 多路复用程序的监听范围内,并对事件和事件处理器进行关联
aeDeleteFileEvent:套接字描述符、事件类型作为参数,让 I / O 多路复用程序取消对给定套接字的给定事件的监听,并取消事件和事件处理器之间的关联
aeGetFileEvents:套接字描述符作为参数,返回该套接字正在被监听的事件类型
aeWait:套接字描述符、事件类型、毫秒数为参数,在给定的时间内阻塞并等待套接字的给定类型事件的产生,当事件产生成功或者等待超时则返回
ae.ApiPoll:sys/time.h/struct timeval 作为参数,在指定时间内,阻塞并等待所有被 aeCreateFileEvent 设置为监听状态的套接字产生的文件事件,当有文件事件产生或者等待超时则返回
aeProcessEvents:文件事件分派器,调用 aeApiPoll 等待事件,再遍历已产生事件,调用响应事件处理器处理
aeGetApiName:返回 I / O 多路复用程序底层所使用的 I / O 多路复用函数库的名称
networking.c/acceptTcpHandler 函数是 Redis 的连接应答处理器,为 sys/socket.h/accept 函数的包装
初始化时,此处理器会和服务器监听套接字的 AE_READABLE 事件关联,当有客户端用 sys/socket.h/connect 函数连接服务器监听套接字时,套接字产生 AE_READBALE 事件,引发连接应答处理器执行
networking.c/readQueryFromClient 是命令请求处理器,负责从套接字中读入客户端发送的命令请求内容,为 unistd.h/read 函数的包装
当客户端已经通过连接应答处理器连接到服务器之后,此处理器会和服务器监听套接字的 AE_READABLE 事件关联,当客户端发送命令请求后,套接字产生 AE_READBALE 事件,引发命令请求处理器执行
networking.c/sendREplyToClient 为命令回复处理器,为 unistd.h/write 的包装
当服务器有命令回复给客户端时,AE_WRITEABLE 事件和命令回复处理器关联
命令回复完毕之后会解除关联
假设 Redis 服务器在运行,监听套接字的 AE_READABLE 事件在监听下,对应处理器为连接应答处理
如果一个 Redis 客户端向服务器发起连接,监听套接字会产生 AE_READABLE 事件,触发连接应答处理器执行,处理器会对请求进行应答,然后创建客户端套接字,以及客户端状态,并将 AE_READABLE 事件与命令处理器关联,使得客户端可以向主服务器发送命令请求
客户端向服务器发送命令请求,客户端则产生 AE_READABLE 事件,引发命令请求处理器执行,处理器读取了客户端的命令内容,传给相关程序去运行
执行命令产生了命令回复,服务器将客户端套接字的 AE_WRITEABLE 事件与命令回复处理器关联,客户端进行读取产生此事件,命令回复处理器将内容写进套接字,服务器最后解除客户套接字的 AE_WRITABLE 事件与命令回复处理器之间的关联
Redis 时间事件分为:
时间事件三个属性组成:
时间事件的返回值决定时间事件的分类:
Redis 3.0 只使用 周期性事件,无定时事件
所有时间事件存放在无序链表中,时间事件执行器遍历整个链表,查找已经到达的时间事件,调用相应的事件处理器
无序指when属性无效,链表实际按 ID 排序
3.0 版本,正常模式下的 Redis 服务器只使用 serverCron 一个时间事件,在 benchmark 模式下,也只使用两个时间事件,这种情况下无序链表的长度很短,不会影响事件执行性能
ae.c
aeCreateTimeEvent:毫秒数,时间事件处理器为参数,将新的时间事件添加到服务器
aeDeleteFileEvent:时间事件 ID ,删除对应的时间事件
aeSearchNearestTimer:返回到达时间距离当前时间最接近的时间事件
processTimeEvents:时间事件的执行器,遍历所有已到达的时间事件,调用事件的处理器
已到达:when 属性的时间戳小于等于当前时间的时间戳
伪代码:
redis.c/serverCron :定期对服务器自身资源和状态进行检查和调整,确保服务器长期、稳定地运行
更新服务器的各类统计信息:时间、内存占用、数据库占用等
清理数据库中的过期键值对
关闭和清理失效的客户端
尝试进行 AOF 或者 RDB 持久化操作
主服务器需要对从服务器进行定期同步
集群模式:对集群进行定期同步和连接测试
周期性事件运行 serverCron,hz 选项调整 serverCron 每秒执行次数
ae.c/aeProcessEvents 函数复杂调度文件事件和时间事件
将 aeProcessEvents 放在一个主循环中,再加上初始化和清理函数,构成了简化的 Redis 服务器的主函数
事件调度和执行规则:
aeApiPoll 最大阻塞时间由已到达的时间最接近当前时间的时间事件决定,可以避免服务器对时间事件频繁的轮询,也可确保函数不会阻塞过长时间
文件事件是随机的,如果等待并处理完文件事件后没有时间事件到达,则再次等待并处理文件事件,不断执行直到时间事件到到达时间,则处理时间事件
对两种事件的处理都是同步、有序、原子地,不会中断、其他事件抢占,尽可能地减少阻塞时间,并在有需要让出执行权,避免饥饿;如命令回复写入客户端套接字数据超过预设量则使用 break 跳出循环,等待下次;时间事件则将耗时的持久化操作放在子进程、子线程执行
因为时间事件在文件事件之后执行,且事件之间不抢占,所以实际 的时间事件处理时间是稍晚的
原文:https://www.cnblogs.com/zephxu/p/14928464.html