关于:Redis 到底是单线程还是多线程?

来自Wikioe
跳到导航 跳到搜索


“Redis 是不是单线程”

“Redis 是不是单线程”是最最最常问的一个问题,不能简而言之地用“是”或“不是”来描述。

Redis 是单线程的吗?

执行 Redis 命令的核心模块是单线程的,而不是整个 Redis 实例就一个线程,Redis 其他模块还有各自模块的线程的。

什么是“单线程模型”?

“Redis 是单线程模型”指的是:

Redis 客户端对服务端的每次调用都经历了:“发送命令”,“执行命令”,“返回结果”三个过程。

其中“执行命令”阶段,由于 Redis 是单线程来处理命令的:
   所有到达服务端的命令都不会立刻执行,所有的命令都会进入一个队列中,然后逐个执行,并且多个客户端发送的命令的执行顺序是不确定的,但是可以确定的是不会有两条命令被同时执行,不会产生并发问题。

这就是 Redis 的单线程基本模型。

Redis “单线程”到底指什么?

Redis 服务器通过 socket(套接字)与客户端或其他 Redis 服务器进行连接,而“文件事件”就是“服务器对 socket 操作的抽象”。服务器与客户端或其他服务器的通信会产生相应的文件事件,而服务器通过监听并处理这些事件来完成一系列网络通信操作。

Redis 基于“Reactor模式”开发了“网络事件处理器”,这个处理器被称为“文件事件处理器”:

文件事件处理器使用 I/O 多路复用程序来同时监听多个 socket,并根据 socket 目前执行的任务来为 socket 关联不同的事件处理器。当被监听的 socket 准备好执行连接应答、读取、写入、关闭等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用 socket 之前已关联好的事件处理器来处理这些事件。


“文件事件处理器”的组成结构为4部分:

  1. 多个套接字、
  2. IO多路复用程序、
  3. 文件事件分派器、
  4. 事件处理器。

结构如下:

Redis:“文件事件处理器结构”.png


综上所述,“单线程”指的是:“文件事件处理器”分派器队列的消费是单线程的。正因为如此,所以 Redis 才叫单线程模型。

什么是“I/O多路复用”技术?

Redis 采用网络 I/O 多路复用技术,来保证在多连接的时候系统的高吞吐量。

关于 I/O 多路复用(又被称为“事件驱动”),首先要理解的是:
   操作系统为你提供了一个功能,当你的某个 socket 可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的 socket 使用时,只有当系统通知我哪个'''描述符'''可读了,我才去执行 read 操作,可以保证每次 read 都能读到有效数据而不做纯返回 -1 和 EAGAIN 的无用功,写操作类似。

   操作系统的这个功能是通过 select / poll / epoll / kqueue 之类的“系统调用函数”来实现,这些函数都可以同时监视多个描述符的读写就绪状况,这样,【多个描述符的 I/O 操作都能在一个线程内并发交替地顺序完成,这就叫 I/O 多路复用。】

简而言之,“I/O多路复用”就是复用一个线程来完成多路(多个socket)的 I/O 请求

  • 多路:指的是多个 socket 连接,
  • 复用:指的是复用同一个 Redis 处理线程。
  • 多路复用主要有三种技术:select,poll,epoll。
    • epoll 是最新的也是目前最好的多路复用技术。
Redis:“IO多路复用”技术.png


采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 I/O 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响 Redis 性能的瓶颈,基于这两点 Redis 具有很高的吞吐量。

“Redis”为什么使用单线程

Redis 利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。

为什么使用单线程?

官方答案:

因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。


具体来讲:

  1. 单线程避免了不必要的:上下文切换(切换线程导致的 CPU 消耗)和竞争条件(对资源加锁,请求等待锁);
  2. 单线程多进程的集群方案,相比于单机多线程更加有效;

为什么不采用多进程或多线程处理?

  1. 多线程处理可能涉及到锁。
  2. 多线程处理会涉及到线程切换而消耗 CPU。

单线程处理的缺点?

  1. 耗时的命令会导致并发的下降,不只是读并发,写并发也会下降。
  2. 无法发挥多核 CPU 性能。
    • 不过可以通过在单机开多个 Redis 实例来完善。

Redis 不存在线程安全问题?

Redis 采用了线程封闭的方式,把任务封闭在一个线程,自然避免了线程安全问题。

不过对于需要依赖多个 Redis 操作(即多个 Redis 操作命令)的复合操作来说,依然需要锁,而且有可能是分布式锁。【???】


Redis 为什么快?

Redis 的高并发和快速原因:

  1. 基于内存,内存的读写速度非常快。
  2. 单线程,避免了不必要的上下文切换(多进程切换消耗CPU)和竞争条件(多线程竞争资源需要加锁等待锁)。
  3. 多路复用技术,可以处理并发的连接。
    • 非阻塞 IO 的实现采用 epoll:epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,减少网络 I/O 的时间消耗。


另外:

  1. 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的。
  2. Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

Redis 不仅仅是单线程

如上所述,一般来说 Redis 的瓶颈并不在 CPU,而在内存和网络。如果要使用 CPU 多核,可以搭建多个 Redis 实例来解决。


其实,Redis 4.0 开始就有多线程的概念了,比如 Redis 通过多线程方式在后台删除对象、以及通过 Redis 模块实现的阻塞命令等:

Redis:多线程.png

另外,Redis 6 中也有多线程IO:

Redis:Threaded IO.png

这个“Theaded IO”指的是在网络 IO 处理方面上了多线程,如网络数据的读写和协议解析等。


但,无论如何,执行命令的核心模块还是单线程的

为什么网络处理要引入多线程?

因为:

Redis 的瓶颈并不在 CPU,而在内存和网络”

内存不够的话,可以加内存或者做数据结构优化和其他优化等。

但网络的性能优化才是最大问题,网络 I/O 的读写在 Redis 整个执行期间占用了大部分的 CPU 时间,如果把网络处理这部分做成多线程处理方式,那对整个 Redis 的性能会有很大的提升。


Redis 单/多线程情况下的 get/set 操作性能做了对比:

Redis:单、多线程情况下的“get、set”性能对比 1.png
Redis:单、多线程情况下的“get、set”性能对比 2.png

从上面的性能测试图来看,多线程的性能几乎是单线程的两倍了。

  • 6.0 版本中,IO 多线程处理模式默认是不开启的,需要去配置文件中开启并配置线程数。