前言
Redis 内部消息通信机制学习和了解
@
目录一、Redis中事件的分类
Redis是一个事件驱动程序,服务器需要处理以下两种事件:
- 文件事件:Redis服务器通过套接字与客户端进行连接,文件事件就是对套接字操作的抽象。服务器和客户端的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件并分配相应的事件处理器来完成一系列网络通信操作。
- 时间事件:主要是定时事件和周期事件。
文件事件
- Redis中的文件事件处理器使用 I/O 多路复用程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器;
- 文件事件处理器以单线程的方式运行,但是通过使用 I/O 多路复用程序来监听多个套接字,这样既实现了高效的网络通信,又能够很好的与Redis服务器中其他同样以单线程方式运行的模块进行对接,保持了Redis内部的单线程设计。
文件事件处理器
文件事件处理器 的组成:
- 套接字:监听客户端的一些操作,Redis中有对应多种操作的套接字,这些套接字分别绑定了相关的事件,比如accept、read、write、close等操作;
- I/O多路复用程序:文件事件处理器通过IO多路复用程序来监听多个套接字,并且根据套接字目前执行的任务来关联不同的事件处理器;
- 文件事件分派器:根据事件分派处理器处理;
- 事件处理器:命令请求处理器、命令回复处理器、连接应答处理器等等,最常见的是连接应答处理器;
当多个文件事件并发地出现,尽管 I/O 多路复用程序总是会将所有产生事件的套接字都放到一个队列里面,然后通过这个队列,以有序、同步、每次一个套接字的方式向文件事件分派器传送套接字,每当上一个套接字产生的事件处理完毕之后,I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字。
I/O 多路复用程序的实现
Redis中 I/O 多路复用程序的所有功能都是通过包装常见的 select、epoll、evport、kqueue 这些 I/O 多路复用函数库来实现的。同时 Redis 的实现源码中通过 #include
宏定义了相应的规则,程序会在编译时自动选择系统中性能最高的 I/O 多路复用函数库来作为Redis 的I/O多路复用程序的底层实现。
文件事件处理器
Redis 对应每种事件都有相应的文件事件处理器,比如说:
- 为了对连接服务器的各个客户端进行应答,服务器要为监听套接字关联连接应答处理器;
- 为了接受客户端传来的命令请求,服务器要为客户端套接字关联命令回复处理器;
- 当主服务器和从服务器进行复制操作时,主从服务器都需要关联复制处理器;
连接应答处理器:
连接应答处理器是通过调用 acceptTcpHandler
函数来实现的,这个处理器用于对连接服务器监听套接字的客户端进行应答,具体是通过封装了 accpet
函数。
当Redis服务器进行初始化时,程序会将这个连接应答处理器和服务器监听套接字的 AE_READABLE
事件关联起来,当客户端用 connect
函数连接服务器监听套接字时,套接字就会产生 AE_READABLE
事件,引发连接应答处理器执行。
命令请求处理器:
函数 readQueryFromClient
是 Redis 的命令请求处理器,这个处理器负责从套接字中读入客户端发送的命令请求内容,具体是 read
函数的封装。
当一个客户端通过连接应答处理器成功连接到服务器之后,服务器会将客户端套接字的 AE_READABLE
事件和命令请求处理器关联起来,当客户端发送命令请求时,套接字就会产生 AE_READABLE
事件,引发命令请求处理器执行,并执行相应的套接字读入操作。
命令回复处理器:
底层调用 sendReplyToClient
函数,当服务器有命令需要回复给客户端时,服务器会将客户端套接字的 AE_WRITEABLE
事件和命令回复处理器关联起来,当客户端准备好接收服务器传回的命令回复时,就会产生 AE_WRITEABLE
事件,引发命令回复处理器执行,并执行相应的套接字写入操作。
完整的客户端和服务器连接示例:
客户端向服务器发送连接请求,监听套接字将产生 AE_READABLE
事件,触发连接应答处理器执行。处理器会对客户端的连接请求进行应答,然后创建客户端套接字,以及客户端状态,并将客户端套接字的 AE_READABLE
事件与命令请求处理器进行关联,使得客户端可以向主服务器发送命令请求。
之后当客户端发送一个命令请求时,客户端套接字会产生 AE_READABLE
事件,引发命令请求处理器执行,处理器读取客户端的命令内容,然后交给程序去执行。
执行命令将产生相应的命令回复,为了将这些命令回复传送回客户端,服务器会将客户端套接字的 AE_WRITEABLE
事件与命令回复处理器进行关联,当客户端尝试读取命令回复时,客户端套接字将产生 AE_WRITEABLE
事件,触发命令回复处理器执行。
时间事件
Redis 中的时间事件具体包括:定时事件、周期事件
一个时间事件主要组成包括:
id
:服务器为时间事件创建的全局唯一 ID。when
:毫秒精度的 Unix 时间戳,记录了时间事件的到达时间;timeProc
:时间事件处理器,是一个函数,当时间事件到达时,服务器就会调用相应的处理器来处理事件。
时间事件怎么处理
服务器将所有的时间事件都放到一个无序链表中,每当时间事件执行器运行时,它就遍历整个链表,查找已经到达的时间事件,调用相应的时间事件处理器执行。
无序在这里是指不按照时间事件的 when
属性字段来排序,所以在处理时需要遍历链表来找到待处理的时间事件。
这种无序链表结构并不会影响时间事件处理器的性能,因为正常情况下 Redis的服务器只会使用 serverCron
一个时间事件,这种情况下几乎将无序链表退化成一个指针来使用,所以使用无序链表来保存时间事件并不会影响效率。
关键的时间事件函数 —— serverCron
Redis在运行时需要定期对自身的资源和状态进行检查和调整,来保证服务长期稳定地运行,这些操作就由 serverCron
函数负责执行,它的主要工作包括:
- 更新服务器的各类统计信息,比如时间、内存占用、数据库占用情况等;
- 清理数据库中过期的键值对;
- 进行 AOF或者 RDB 持久化操作;
- 如果是主服务器,要对从服务器进行定期同步;
- 如果处于集群模式,对集群进行定期同步的连接测试。
serverCron
函数默认每秒钟运行10次。
事件调度 —— aeProcessEvents
Redis 中的时间事件和文件事件的调度是由 aeProcessEvents
函数来实现的,它对于两种事件的处理都是同步、有序、原子地执行的,服务器不会中途中断事件处理,也不会对事件进行抢占。所以两种事件处理器都会尽可能地减少程序地阻塞时间,并且在有需要时主动让出执行权,从而降低造成事件饥饿的可能性。