一文弄懂Redis 线程模型

 更新时间:2024年02月12日 09:47:26   作者:程序猿进阶  
使用Redis 时,几乎不存在 CPU 成为瓶颈的情况, 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 多路复用程序
  • 文件事件分派器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

img

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

img

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

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

img

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】APIae.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 所示。

img

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

img

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

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

img

【1】客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 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 线程模型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • Redis中AOF与RDB持久化策略深入分析

    Redis中AOF与RDB持久化策略深入分析

    Redis作为一款内存数据库,因为是内存读写,所以性能很强,但内存存储是易失性的,断电或系统奔溃都会导致数据丢失,因此Redis也需要将其数据持久化到磁盘上面,当Redis服务重启时,会把磁盘上的数据再加载进内存,Redis提供了两种持久化机制-RDB快照和AOF日志
    2022-11-11
  • 一文带你了解Redis怎么启动以及使用

    一文带你了解Redis怎么启动以及使用

    对于Redis我们一般会使用到三种启动方式:直接启动、指定配置文件启动、开机自启动,下面这篇文章主要给大家介绍了关于Redis怎么启动以及使用的相关资料,需要的朋友可以参考下
    2023-04-04
  • Redis和Nginx实现限制接口请求频率的示例

    Redis和Nginx实现限制接口请求频率的示例

    限流就是限制API访问频率,当访问频率超过某个阈值时进行拒绝访问等操作,本文主要介绍了Redis和Nginx实现限制接口请求频率的示例,具有一定的参考价值,感兴趣的可以了解一下
    2024-02-02
  • 浅谈Redis缓存有哪些淘汰策略

    浅谈Redis缓存有哪些淘汰策略

    redis用做缓存是一种非常常见的手段,然而由于内存大小的限制,会导致redis在内存空间满了以后需要处理继续存入的数据,所以就需要淘汰策略,本文就详细的介绍一下
    2021-08-08
  • 详解Redis数据结构之跳跃表

    详解Redis数据结构之跳跃表

    这篇文章主要介绍了Redis数据结构中的跳跃表的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • redis key键过期删除策略及淘汰机制探究

    redis key键过期删除策略及淘汰机制探究

    这篇文章主要为大家介绍了redis key键过期删除策略及淘汰机制探究,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • 在Centos 8.0中安装Redis服务器的教程详解

    在Centos 8.0中安装Redis服务器的教程详解

    由于考虑到linux服务器的性能,所以经常需要把一些中间件安装在linux服务上,今天通过本文给大家介绍下在Centos 8.0中安装Redis服务器的详细过程,感兴趣的朋友一起看看吧
    2022-03-03
  • 配置Redis序列化方式不生效问题及解决

    配置Redis序列化方式不生效问题及解决

    这篇文章主要介绍了配置Redis序列化方式不生效问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • 如何提高Redis服务器的最大打开文件数限制

    如何提高Redis服务器的最大打开文件数限制

    文章讨论了如何提高Redis服务器的最大打开文件数限制,以支持高并发服务,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2025-01-01
  • Windows中redis设置密码的两种方法

    Windows中redis设置密码的两种方法

    之前写的一个项目,有项目代码,有数据库,但是本地没redis,没法跑此项目,故思考在本地安装一个redis做登录session存储,所以开始动手实践,下面这篇文章主要给大家介绍了关于Windows中redis设置密码的两种方法,需要的朋友可以参考下
    2023-04-04

最新评论