Redis-IO模型&多线程


  • Redis I/O 多路复用
  • Redis 多线程
  • Reactor 模式

I/O 多路复用

  • Redis 基于 Reactor 模式开发了自己的网络事件处理器
  • I/O 多路复用同时监听多个 socket,根据socket 当前执行的事件来为 socket 选择对应的事件处理器
    • 当被监听的 socket 准备好执行accept、read、write、close 等操作时,和操作对应的文件事件就会产生,这时 FEH 就会调用 socket 之前关联好的事件处理器来处理对应事件
  • 文件事件处理器(file event handler,后文简称为 FEH)是单线程的,所以 redis 设计为单线程模型
  • 虽然 FEH 是单线程运行,但通过 I/O 多路复用监听多个 socket,不仅实现高性能的网络通信模型,又能和 Redis 服务器中其它同样单线程运行的模块交互,保证了Redis 内部单线程模型的简洁设计
  • socket
    • 文件事件就是对 socket 操作的抽象, 每当一个 socket 准备好执行连接accept、read、write、close 等操作时, 就会产生一个文件事件。一个服务器通常会连接多个socket, 多个 socket 可能并发产生不同操作,每个操作对应不同文件事件
  • I/O 多路复用程序:负责监听多个 socket
    • 尽管文件事件可能并发出现, 但 I/O 多路复用程序会将所有产生事件的socket 放入队列, 通过该队列以有序、同步且每次一个 socket 的方式向文件事件分派器传送 socket
    • 当上一个 socket 产生的事件被对应事件处理器执行完后, I/O 多路复用程序才会向文件事件分派器传送下个 socket
  • Redis 的 I/O 多路复用程序的所有功能都是通过包装常见的 select、epoll、evport 和 kqueue 这些 I/O 多路复用函数库实现的
    • 编译时自动选择系统中性能最高的I/O 多路复用函数库作为 Redis 的 I/O 多路复用程序的底层实现:性能降序排列
      • Evport,Epoll 和 KQueue 具有 O(1)描述符选择算法复杂度,可以提供很多(数十万个)文件描述符
      • select 复杂性是 O(n),最多只能提供 1024 个描述符
  • 文件事件分派器:接收 I/O 多路复用程序传来的 socket, 并根据 socket 产生的事件类型, 调用相应的事件处理器
  • 文件事件处理器:服务器会为执行不同任务的套接字关联不同的事件处理器
  • 文件事件的类型
    • I/O 多路复用程序可以监听多个 socket 的 ae.h/AE_READABLE 事件和ae.h/AE_WRITABLE 事件
    • 当 socket 可读(比如客户端对 Redis 执行write/close 操作),或有新的可应答的socket 出现时(即客户端对 Redis 执行 connect 操作),socket 就会产生一个AE_READABLE 事件
    • 当 socket 可写时(比如客户端对 Redis 执行read 操作),socket 会产生一个AE_WRITABLE 事件
    • I/O 多路复用程序可以同时监听 AE_REABLE 和AE_WRITABLE 两种事件,要是一个socket 同时产生这两种事件,那么文件事件分派器优先处理 AE_REABLE 事件
    • 即一个socket 又可读又可写时, Redis 服务器先读后写 socket
  • 客户端和 Redis 服务器通信的整个过程
    • Redis 启动初始化时,将连接应答处理器跟 AE_READABLE 事件关联
    • 若一个客户端发起连接,会产生一个 AE_READABLE 事件,然后由连接应答处理器负责和客户端建立连接,创建客户端对应的 socket,同时将这个 socket的AE_READABLE 事件和命令请求处理器关联,使得客户端可以向主服务器发送命令请求
    • 当客户端向 Redis 发请求时(不管读还是写请求),客户端 socket 都会产生一个AE_READABLE 事件,触发命令请求处理器。处理器读取客户端的命令内容,然后传给相关程序执行
    • 当 Redis 服务器准备好给客户端的响应数据后,会将 socket 的AE_WRITABLE事件和命令回复处理器关联,当客户端准备好读取响应数据时,会在 socket 产生一个AE_WRITABLE 事件,由对应命令回复处理器处理,即将准备好的响应数据写入socket,供客户端读取
    • 命令回复处理器全部写完到 socket 后,就会删除该socket 的AE_WRITABLE事件和命令回复处理器的映射

Redis 多线程

  • 严格来讲从 Redis4.0 之后并不是单线程,除了主线程外,它也有后台线程在处理一些较为缓慢的操作,例如清理脏数据、无用连接的释放、大 key 的删除等等
  • 通过AE 事件模型以及 IO 多路复用等技术,处理性能非常高,因此没有必要使用多线程
    • Hash 的惰性Rehash、Lpush 等等 “线程不安全” 的命令都可以无锁进行
  • 读写网络的 read/write 系统调用占用了Redis执行期间大部分CPU 时间,瓶颈主要在于网络的 IO 消耗
  • io-threads-do-reads yes 开启多线程
    • io-threads 4 开启多线程后,还需要设置线程数,否则是不生效的
    • 线程数一定要小于机器核数
      • 4 核的机器建议设置为2 或3 个线程
      • 8 核的建议设置为 6 个线程
      • 超过了 8 个基本就没什么意义了
  • 至少要 4 核的机器,且 Redis 实例已经占用相当大的 CPU耗时的时候才建议采用,否则使用多线程没有意义
  • 多线程的实现机制
    • 主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列
    • 主线程处理完读事件之后,通过 RR(Round Robin) 将这些连接分配给这些 IO 线程
    • 主线程阻塞等待 IO 线程读取 socket 完毕
    • 主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行回写 socket
    • 主线程阻塞等待 IO 线程将数据回写 socket 完毕
    • 解除绑定,清空等待队列
  • 特点
    • IO 线程要么同时在读 socket,要么同时在写,不会同时读或写
    • IO 线程只负责读写 socket 解析命令,不负责命令处理
  • 不需要去考虑控制key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题
    • Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行

Reactor 模式

单线程 Reactor 模式:
单线程 Reactor 模式

单线程 Reactor + 工作者线程池:
单线程 Reactor + 工作者线程池

多 Reactor 线程模式:
多 Reactor 线程模式


文章作者: 钱不寒
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 钱不寒 !
  目录