一文弄懂Redis 线程模型

2024-03-01 0 621
目录
  • 一、概述
  • 二、文件事件处理器的结构
  • 三、客户端与redis的一次通信过程
  • 四、为啥 redis 单线程模型也能效率这么高

一、概述

【1】Redis是基于Reactor模式开发的网络事件处理器:这个处理器被称为文件事件处理器(file event handler),这个文件事件处理器是单线程的,所以Redis才叫做单线程的模型:

  • 文件事件处理器使用I/O多路复用(multiplexing)机制监听多个套接字Socket,根据Socket上的事件来选择对应的事件处理器进行处理。
  • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时。与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

【2】虽然文件事件处理器以单线程的方式运行,但其使用I/O多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与Redis服务器中其他同样以单线程方式运行的模块进行对接,这保持了Redis内部单线程设计的简单性。

二、文件事件处理器的结构

【1】文件事件处理器的结构包含 4 个部分:

  • 多个socket
  • IO多路复用程序
  • 文件事件分派器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

一文弄懂Redis 线程模型

【2】多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,会将socket产生的事件放入队列中排队,以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字。当上一个套接字产生的事件被处理完毕之后(该套接字为事件所关联的事件处理器执行完毕),I/O多路复用程序才会继续向文件事件分派器传送下一个套接字, 如图:

一文弄懂Redis 线程模型

文件事件分派器接收I/O多路复用程序传来的套接字, 并根据套接字产生的事件的类型, 调用相应的事件处理器。服务器会为执行不同任务的套接字关联不同的事件处理器, 这些处理器是一个个函数, 它们定义了某个事件发生时, 服务器应该执行的动作。

【3】I/O多路复用程序的实现:Redis的I/O多路复用程序的所有功能都是通过包装常见的select、epoll、evport和kqueue这些I/O多路复用函数库来实现的, 每个I/O多路复用函数库在 Redis 源码中都对应一个单独的文件, 比如ae_select.c、ae_epoll.c、ae_kqueue.c, 诸如此类。因为Redis为每个I/O多路复用函数库都实现了相同的API, 所以I/O多路复用程序的底层实现是可以互换的, 如下图所示:

一文弄懂Redis 线程模型

Redis在I/O多路复用程序的实现源码中用#include宏定义了相应的规则, 程序会在编译时自动选择系统中性能最高的I/O多路复用函数库来作为Redis的I/O多路复用程序的底层实现:

/* 包括此系统支持的最佳复用层。
* 以下应按性能降序排列。 */
#ifdef HAVE_EVPORT
#include \”ae_evport.c\”
#else
#ifdef HAVE_EPOLL
#include \”ae_epoll.c\”
#else
#ifdef HAVE_KQUEUE
#include \”ae_kqueue.c\”
#else
#include \”ae_select.c\”
#endif
#endif
#endif

【4】事件的类型:I/O多路复用程序可以监听多个套接字的ae.h/AE_READABLE事件和ae.h/AE_WRITABLE事件, 这两类事件和套接字操作之间的对应关系如下:

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

I/O多路复用程序允许服务器同时监听套接字的AE_READABLE事件和AE_WRITABLE事件, 如果一个套接字同时产生了这两种事件, 那么文件事件分派器会优先处理AE_READABLE事件, 等到AE_READABLE事件处理完之后, 才处理AE_WRITABLE事件。这也就是说, 如果一个套接字又可读又可写的话, 那么服务器将先读套接字, 后写套接字。

【5】API:ae.c/aeCreateFileEvent函数接收一个套接字描述符、 一个事件类型、 以及一个事件处理器作为参数, 将给定套接字的给定事件加入到I/O多路复用程序的监听范围之内, 并对事件和事件处理器进行关联。ae.c/aeDeleteFileEvent函数接收一个套接字描述符和一个监听事件类型作为参数, 让I/O多路复用程序取消对给定套接字的给定事件的监听, 并取消事件和事件处理器之间的关联。ae.c/aeGetFileEvents函数接收一个套接字描述符, 返回该套接字正在被监听的事件类型:

  • 如果套接字没有任何事件被监听, 那么函数返回AE_NONE;
  • 如果套接字的读事件正在被监听, 那么函数返回AE_READABLE;
  • 如果套接字的写事件正在被监听, 那么函数返回AE_WRITABLE;
  • 如果套接字的读事件和写事件正在被监听, 那么函数返回AE_READABLE | AE_WRITABLE;

ae.c/aeWait函数接受一个套接字描述符、一个事件类型和一个毫秒数为参数, 在给定的时间内阻塞并等待套接字的给定类型事件产生, 当事件成功产生, 或者等待超时之后, 函数返回。

ae.c/aeApiPoll函数接受一个sys/time.h/struct timeval结构为参数, 并在指定的时间內, 阻塞并等待所有被aeCreateFileEvent函数设置为监听状态的套接字产生文件事件, 当有至少一个事件产生, 或者等待超时后, 函数返回。ae.c/aeProcessEvents函数是文件事件分派器, 它先调用aeApiPoll函数来等待事件产生, 然后遍历所有已产生的事件, 并调用相应的事件处理器来处理这些事件。

ae.c/aeGetApiName函数返回I/O多路复用程序底层所使用的I/O多路复用函数库的名称: 返回"epoll"表示底层为epoll函数库, 返回"select"表示底层为select函数库, 诸如此类。

【6】文件事件的处理器:Redis为文件事件编写了多个处理器, 这些事件处理器分别用于实现不同的网络通讯需求, 比如:

  • 为了对连接服务器的各个客户端进行应答, 服务器要为监听套接字关联连接应答处理器;
  • 为了接收客户端传来的命令请求, 服务器要为客户端套接字关联命令请求处理器;
  • 为了向客户端返回命令的执行结果, 服务器要为客户端套接字关联命令回复处理器;
  • 当主服务器和从服务器进行复制操作时, 主从服务器都需要关联特别为复制功能编写的复制处理器;

在这些事件处理器里面, 服务器最常用的要数与客户端进行通信的连接应答处理器、 命令请求处理器和命令回复处理器。

【7】连接应答处理器:networking.c/acceptTcpHandler函数是Redis的连接应答处理器, 这个处理器用于对连接服务器监听套接字的客户端进行应答, 具体实现为sys/socket.h/accept函数的包装。当Redis服务器进行初始化的时候, 程序会将这个连接应答处理器和服务器监听套接字的AE_READABLE事件关联起来, 当有客户端用sys/socket.h/connect函数连接服务器监听套接字的时候, 套接字就会产生AE_READABLE事件, 引发连接应答处理器执行, 并执行相应的套接字应答操作, 如图IMAGE_SERVER_ACCEPT_CONNECT所示。

一文弄懂Redis 线程模型

【8】命令请求处理器:networking.c/readQueryFromClient函数是 Redis 的命令请求处理器, 这个处理器负责从套接字中读入客户端发送的命令请求内容, 具体实现为unistd.h/read函数的包装。当一个客户端通过连接应答处理器成功连接到服务器之后, 服务器会将客户端套接字的AE_READABLE事件和命令请求处理器关联起来, 当客户端向服务器发送命令请求的时候, 套接字就会产生AE_READABLE事件, 引发命令请求处理器执行, 并执行相应的套接字读入操作, 如图IMAGE_SERVER_RECIVE_COMMAND_REQUEST所示。

一文弄懂Redis 线程模型

当命令回复发送完毕之后, 服务器就会解除命令回复处理器与客户端套接字的AE_WRITABLE事件之间的关联。

三、客户端与redis的一次通信过程

一文弄懂Redis 线程模型

【1】客户端socket01向redis的serversocket请求建立连接,此时serversocket会产生一个AE_READABLE事件,IO多路复用程序监听到serversocket产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的socket01,并将该socket01的AE_READABLE事件与命令请求处理器关联。

【2】假设此时客户端发送了一个set key value请求,此时redis中的socket01会产生AE_READABLE事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面socket01的AE_READABLE事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取socket01的key value并在自己内存中完成key value的设置。操作完成后,它会将socket01的AE_WRITABLE事件与命令回复处理器关联。

【3】如果此时客户端准备好接收返回结果了,那么redis中的socket01会产生一个AE_WRITABLE事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对socket01输入本次操作的一个结果,比如ok,之后解除socket01的AE_WRITABLE事件与命令回复处理器的关联。这样便完成了一次通信。

四、为啥 redis 单线程模型也能效率这么高

  • 纯内存操作
  • 核心是基于非阻塞的IO多路复用机制
  • 单线程反而避免了多线程的频繁上下文切换问题

到此这篇关于一文弄懂Redis 线程模型的文章就介绍到这了,更多相关Redis 线程模型内容请搜索悠久资源以前的文章或继续浏览下面的相关文章希望大家以后多多支持悠久资源!

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悠久资源 Redis 一文弄懂Redis 线程模型 https://www.u-9.cn/database/redis/176539.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务