首先要明确,redis单线程指的是redis对外提供键值存储服务的主要流程 :Redis的网络IO和键值对读写是由一个线程完成的。

但Redis的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。

另外,Redis 6.0 之后的版本却抛弃了单线程模型这一设计,所以现在的redis不是单线程的。

接下来主要讨论Redis6.0之前为什么用单线程、为什么单线程还这么快(多路复用)。

为什么使用单线程?

其实这个问题,就是在讨论多线程的缺点。

多线程的优点:可以增加系统吞吐率,或是可以增加系统扩展性。

但以上优点是基于良好的系统设计而言的,否则们刚开始增加线程数时,系统吞吐率会增加,但是,再进一步增加线程时,系统吞吐率就增长迟缓了。

因为如果系统中有某个资源被多线程同时访问时,为了保证共享资源的正确性,就会带来额外的开销。即多线程编程模式面临的共享资源的并发访问控制问题。

而同时,多线程技术能够帮助我们充分利用 CPU 的计算资源来并发的执行不同的任务,但CPU 并不是制约 Redis 性能表现的瓶颈所在更多情况下是受到内存大小和网络I/O的限制

因此使用单线程,有以下原因 :

  • Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU;(决定性因素)
  • 使用单线程模型能带来更好的可维护性,方便开发和调试;
  • 使用单线程模型也能并发的处理客户端的请求;

单线程怎么快?

  • Redis 的大部分操作在内存上完成
  • 它采用了高效的数据结构,例如哈希表和跳表
  • Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率。

如下图 :

因此来谈谈多路复用机制。从基本IO模型的阻塞点,到非阻塞模式,再到基于多路复用的高性能 I/O 模型。

基本 IO 模型与阻塞点

以 Get 请求为例,为了处理一个 Get 请求,需要

  1. 监听客户端请求(bind/listen)
  2. 客户端建立连接(accept) (阻塞点)
  3. 从 socket 中读取请求(recv) (阻塞点)
  4. 解析客户端发送请求(parse)
  5. 根据请求类型读取键值数据(get)
  6. 给客户端返回结果,即向 socket 中写回数据(send)。

上面的2和3是阻塞点,当 Redis 监听到一个客户端有连接请求,但一直未能成功建立起连接时,会阻塞在 accept() 函数这里,导致其他客户端无法和 Redis 建立连接。类似的,当 Redis 通过 recv() 从一个客户端读取数据时,如果数据一直没有到达,Redis 也会一直阻塞在 recv()。

因此有了非阻塞模式,这是socket网络自带的。

非阻塞模式

Socket 网络模型的非阻塞模式设置,主要体现在三个关键的函数调用上 :

  1. socket() 方法会返回主动套接字
  2. 然后调用 listen() 方法,将主动套接字转化为监听套接字,此时,可以监听来自客户端的连接请求。
  3. 最后,调用 accept() 方法接收到达的客户端连接,并返回已连接套接字

使 accept() 非阻塞 :

针对监听套接字设置非阻塞模式,即 Redis 调用 accept() 但一直未有连接请求到达时,Redis 线程可以返回处理其他操作,而不用一直等待。但是,你要注意的是,调用 accept() 时,已经存在监听套接字了。(虽然 Redis 线程可以不用继续等待,但是总得有机制继续在监听套接字上等待后续连接请求,并在有请求时通知 Redis。)

使recv()/send()非阻塞 :

针对已连接套接字设置非阻塞模式:Redis 调用 recv() 后,如果已连接套接字上一直没有数据到达,Redis 线程同样可以返回处理其他操作。我们也需要有机制继续监听该已连接套接字,并在有数据达到时通知 Redis。

基于多路复用的高性能 I/O 模型

IO 多路复用机制是指一个线程处理多个 IO 流.

简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

为什么引入多线程?

Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。

所以为了提高网络 I/O 的并行度,Redis 6.0 对于网络 I/O 采用多线程来处理。但是对于命令的执行,Redis 仍然使用单线程来处理

(Redis6.0 的多线程默认是禁用的,只使用主线程。)

关于线程数,官网建议一定要小于机器核数,线程数并不是越大越好。