vlambda博客
学习文章列表

浅析Redis网络模型

Redis网络基础架构

Redis网络层基础组件主要包括四个部分:

①EventLoop事件轮询器,这部分实现在AE里面。
②提供Socket句柄事件的多路复用器,这部分分别对于不同平台提供了不同的实现,比如epoll和select可以用于linux平台、kqueue可以用于苹果平台、evpoll可以用于Solaris平台,这里并没有看到iocp,也就是Redis对于Windows支持并不是很好。
③包括网络事件处理器实现的networking,这部分主要包括两个重要的今天要讲的事件处理器:acceptTcpHandler和acceptCommonHandler。
④处理网络比较底层的部分,比如网络句柄创建、网络的读写等。

Redis单线程模型

Redis基于Reactor模式开发了自己的网络事件处理器,被称为文件事件处理器,由套接字、I/O多路复用程序、文件事件分派器(dispatcher),事件处理器四部分组成。

浅析Redis网络模型

浅析Redis网络模型

1、I/O多路复用程序、文件事件分派器
  • I/O多路复用程序会同时监听多个套接字;

  • 当被监听的套接字准备好执行accept、read、write、close等操作时,与操作相对应的文件事件就会产生;

  • I/O多路复用程序会将所有产生事件的套接字都压入一个队列,然后以有序地每次仅一个套接字的方式传送给文件事件分派器;

  • 文件事件分派器接收到套接字后会根据套接字产生的事件类型调用对应的事件处理器。

2、事件的处理器
(1)连接应答处理器:

当Redis服务器进行初始化的时候,程序会将这个连接应答处理器和服务器监听套接字的AE_READABLE事件关联起来;
当有客户端用sys/socket.h/connect函数连接服务器监听套接字的时候,套接字就会产生AE_READABLE事件,引发连接应答处理器执行,并执行相应的套接字应答操作。

(2)命令请求处理器:

当一个客户端通过连接应答处理器成功连接到服务器之后,服务器会将客户端套接字的AE_READABLE事件和命令请求处理器关联起来;
当客户端向服务器发送命令请求的时候,套接字就会产生AE_READABLE事件,引发命令请求处理器执行,并执行相应的套接字读入操作;
在客户端连接服务器的整个过程中,服务器都会一直为客户端套接字的AE_READABLE事件关联命令请求处理器。

(3)命令回复处理器:

当服务器有命令回复需要传送给客户端的时候,服务器会将客户端套接字的AE_WRITABLE事件和命令回复处理器关联起来;
当客户端准备好接收服务器传回的命令回复时,就会产生AE_WRITABLE事件,引发命令回复处理器执行,并执行相应的套接字写入操作。
当命令发送完毕后,服务器会解除命令回复处理器与客户端套接字的AE_WRITABLE事件之间的关联。

  • 注意1:只有当上一个套接字产生的事件被所关联的事件处理器执行完毕,I/O多路复用程序才会继续向文件事件分派器传送下一个套接字,所以对每个命令的执行时间是有要求的,如果某个命令执行过长,会造成其他命令的阻塞。所以慎用O(n)命令,Redis是面向快速执行场景的数据库。

  • 注意2:命令的并发性。Redis是单线程处理命令,命令会被逐个被执行,假如有3个客户端命令同时执行,执行顺序是不确定的,但能确定不会有两条命令被同时执行,所以两条incr命令无论怎么执行最终结果都是2。

3、事件调度

服务器需要处理两类事件:文件事件、时间事件
(1)文件事件:Redis服务器对套接字的操作,当一个套接字准备执行连接、读、写、关闭等操作时就会产生一个文件事件。文件事件分为AE_READABLE和AE_WRITABLE两类。

  • 当套接字变得可读时(客户端对套接字执行write操作,或者执行close操作),或者有新的可应答(acceptable)套接字出现时(客户端对服务器的监听套接字执行connect操作),套接字产生AE_READABLE事件。

  • 当套接字变得可写时(客户端对套接字执行read操作),套接字产生AE_WRITABLE事件。

  • 当一个套接字同时产生了这两种事件,文件事件分派器会优先处理AE_READABLE事件。

(2)时间事件:Redis服务器中一些需要在给定时间点执行的操作。
服务器将所有时间事件放在一个无序链表中,每当时间事件执行器运行时,它就会遍历整个链表,查找所有已经到达的时间事件,并调用相应的事件处理器。
Redis服务器一般情况下只执行serverCron函数一个时间事件,通过redis.c/serverCron函数定期对自身的资源和状态进行检查和调整,主要工作包括:

  • 更新服务器的各类统计信息,如时间、内存占用、数据库占用情况等。

  • 清理数据库中的过期键值对。

  • 关闭和清理连接失效的客户端。

  • 尝试进行AOF或RDB持久化操作。

  • 如果服务器是主服务器,那么对从服务器进行定期同步。

  • 如果处于集群模式,对集群进行定期执行同步和连接测试。

Redis2.6版本,服务器默认serverCron每秒运行10次,平均每隔100毫秒运行一次。Redis2.8版本之后,通过hz调整每秒执行次数。

(3)因为服务器中同时存在文件事件和时间事件,所以服务器必须对这两种事件进行调度,事件的调度和执行由ae.c/aeProcessEvents函数负责,逻辑如下:
先计算最近的时间事件距离到达还有多少毫秒remaind_ms,根据这个值阻塞并等待文件事件产生,remaind_ms<=0不阻塞,阻塞期间会不断处理出现的文件事件。当时间事件最终到达时,服务器才会开始处理达到的时间事件。

  • 注意:对文件事件和时间事件的处理都是同步、有序、原子地执行的,服务器不会中途中断事件处理,也不会对事件进行抢占,因此,不管是文件事件的处理器,还是时间事件的处理器,它们都会尽可能减少程序的阻塞时间,并在有需要时主动让出执行权。

因为时间事件在文件事件之后执行,并且事件之间不会出现抢占,所以时间事件的实际处理时间通常会比设定的到达时间晚一些。
如图,在时间事件到达前(100ms),服务器已经等待并处理了两次文件事件,又因为处理事件的过程中不会出现抢占,所以实际处理时间事件的时间比预计慢了30毫秒。

Redis一次完整的通信请求

① 客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中;
② 文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 AE_READABLE 事件与命令请求处理器关联;
③ 假设此时客户端发送了一个 set key value 请求,此时 redis 中的 socket01 会产生 AE_READABLE 事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面 socket01 的 AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理;
④ 命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。操作完成后,它会将 socket01 的 AE_WRITABLE 事件与命令回复处理器关联;
⑤ 如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 AE_WRITABLE 事件,同样压入队列中;
⑥ 事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok,之后解除 socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。
这样便完成了一次通信。