Redis 是单线程的???
Posted Java知音_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 是单线程的???相关的知识,希望对你有一定的参考价值。
摘要:Redis 单线程/多线程相关内容,包括其中涉及的一些原理等。
前言:相信大家一定和我一样在很多标题党文章中,看到 Redis 是单线程等等相关的说明,但是其中并没系统、完整的总结 Redis 是否真的是单线程,还是 Redis 中的某个模块是单线程。今天我们就一起来揭秘一下这个 “熟悉的陌生人”。
一、Redis 是否真的是单线程?
Redis 服务器
使用单线程单进程
的方式来处理命令请求, 并与多个客户端进行网络通信。Redis 网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。所以说 Redis 不是所有模块都是单线程的。
Redis 服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接,每个客户端可以向服务器发送命令请求,而服务器则接收并处理客户端发送的命令请求, 并向客户端返回命令回复。
Redis 服务器状态结构的 clients 属性是一个链表。这个链表保存了所有与服务器连接的客户端的状态结构,对客户端执行批量操作,或者查找某个指定的客户端,都可以通过遍历 clients 链表来完成。
Redis v4.0 之前的版本,真正的单线程。
Redis v4.0,引入多线程处理 AOF 等任务,但核心的网络模型中依旧使用单线程。
Redis v6.0(正式在网络模型中实现 I/O 多线程)。
从 Redis v1.0 到 Redis v6.0以前,Redis 的核心网络模型一直都是一个典型的单 Reactor 模型,所有的事件都在这个线程内处理完成。
二、Redis 单线程模型
Redis 作为一个成熟的分布式缓存框架,它由很多个模块组成,如网络请求模块、索引模块、存储模块、高可用集群支撑模块、数据操作模块等。
我们所说的 Redis 单线程,指的是"其网络IO和键值对读写是由一个线程完成的",也就是说,Redis中只有网络请求模块和数据操作模块是单线程的。而其他的如持久化存储模块、集群支撑模块等是多线程的。
Redis 客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于 Redis 是单线程来处理命令的,所以每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题。
三、Redis客户端到服务端请求过程
客户端到服务端建立网络连接
客户端发生读写事件并向服务器端发送请求数据
服务端进行数据处理
服务端数据返回
1、客户端到服务端建立网络连接
首先,客户端和服务端是 socket 通信方式,socket 服务端监听可同时接受多个客户端请求,这点很重要,如果不理解可先记住。注意这里可以理解为本质上与 redis 无关,这里仅仅做网络连接,或者可以理解为,为 redis 服务端提供网络交互 api。
假设建立网络连接需要15秒(实际上比这个时间小非常多)。
2、客户端发生读写事件并向服务端发送请求数据
首先确定一点,redis 的客户端与服务端通信是基于 TCP 连接,第一阶段仅仅是建立了客户端到服务端的网络连接,然后才是发生第二阶段的读写事件。
完成了上一个阶段的网络连接,redis 客户端开始真正向服务端发起读写事件,假设是 set(写)事件,此时 redis 客户端开始向建立的网络流中送数据,服务端可以理解为给每一个网络连接创建一个线程同时接收客户端的请求数据。
假设从客户端发数据,到服务端接收完数据需要5秒。
3、Redis 服务器进行数据处理
服务端完成了第二阶段的数据接收,接下来开始依据接收到的数据做逻辑处理,然后得到处理后的数据。数据处理可以理解为一次方法调用,带参调用方法,最终得到方法返回值。不要想复杂,重在理解流程。假设redis服务端处理数据需要0.1秒
4、服务器数据返回
这一阶段很简单,当 reids 服务端数据处理完后 就会立即返回处理后的数据,没什么特别需要强调的。假设服务端把处理后的数据回送给客户端需要5秒。
举例
第一阶段说过,redis 是以 socket 方式通信,socket 服务端可同时接受多个客户端请求连接,也就是说,redis 服务同时面对多个 redis 客户端连接请求,而 redis 服务本身是单线程运行。
假设,现在有 A,B,C,D,E 五个客户端同时发起 redis 请求,A 优先稍微一点点第一个到达,然后是 B,C,D,E 依次到达,此时 redis 服务端开始处理A请求,建立连接需要15秒,获取请求数据需要5秒,然后处理数据需要0.1秒,回送数据给客户端需要5秒,总共大概需要25.1秒。
也就是说,下一个B请求需要等待25.1秒,这里注意,也许这五个几乎同时请求,由于 socket 可以同时处理多个请求,所以建立网络连接阶段时间差可忽略(也就是说少了15秒)
,但是在第二阶段,服务端需要什么事都不干,坐等5秒中,对于 CPU 和客户端来说是无法忍受的。
所以说单线程效率非常,非常低,但是正是因为这些类似问题,redis 单线程本质上并不是如此运行。接下来讨论 redis 真正的单线程运行方式。
四、Redis 单线程运行方式简单理解
客户端与服务端建立连接交由 socket,可以同时建立多个连接(这里应该是多线程/多进程),建立的连接 redis 是知道的,然后 redis 会基于这些建立的连接去探测哪个连接已经接收完了客户端的请求数据(注意:不是探测哪个连接建立好了,而是探测哪个接收完了请求数据
),而且这里的探测动作就是单线程的开始,一旦探测到则基于接收到的数据开始数据处理阶段,然后返回数据,再继续探测下一个已经接收完请求数据的网络连接。注意,从探测到数据处理再到数据返回,全程单线程。
从探测到接受完请求数据的网络连接到最终的数据返回,服务器只需要5.1秒(实际时间远远小于这个),最终的返回数据虽然牵扯到网络,但是网络连接已经建立,这个速度也是非常非常快的,只是比数据处理阶段慢那么一点点。因此单线程方式在效率上其实并不需要担心。
上面说的从探测到返回数据的过程,实际就是 redis 的单线程事件循环,即单 Reactor 模型。
五、Reactor 模型
Reactor 模型本质上指的是使用 I/O多路复用(I/O multiplexing) + 非阻塞 I/O(non-blocking I/O) 的模式
。
Reactor 模型中有三种角色、三种事件、三种模式。
1、三种事件
连接事件: 当一个客户端要和服务器端进行交互时,客户端会向服务器端发送连接请求,以建立连接。
写事件: 一旦连接建立后,客户端会给服务器端发送读请求,以便读取数据。服务器端在处理读请求时,需要向客户端写回数据。
读事件: 无论客户端给服务器端发送读或写请求,服务器端都需要从客户端读取请求内容。
2、三种角色
Reactor:主线程,模型核心,通过事件循环不断处理事件,如果是新的连接事件,则交给 Acceptor,如果是已经连接的 I/O 事件,则交给 Handler;
Acceptor:负责 server 和 client 的连接。Reactor 模式一条最重要的原则就是:I/O 操作不能阻塞主线程循环,所以对于阻塞网络的 I/O,一般都是通过 I/O 多路复用实现的,如 Linux 上的 epoll,这样可以最大程度地满足
“一个线程非阻塞地监听多个 I/O 事件”
。当有新的连接到来是,Acceptor 创建一个新的 socket,并将这个 socket 添加到 epoll 的监听队列中,指定事件类型(读事件 或 写事件),指定对应事件发生时的回调函数,这样当此客户端的请求到来时,epoll 会调用设定好的回调函数(可以理解成 Handler);Handler:真正的业务处理逻辑。已经建立连接的客户端请求到来后,触发 epoll 的读事件,调用 Handler 执行具体的业务逻辑。
3、三种模式
单 Reactor 单线程
单 Reactor 多线程
多 Reactor 多线程/进程
1)、单 Reactor 单线程
单 Reactor 单线程:建立连接(Acceptor)、监听accept、read、write事件(Reactor)、处理事件(Handler)都只用一个单线程。如下图所示:
具体流程:
Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发。
如果是连接建立的事件,则交由 Acceptor 通过 accept 处理连接请求,然后创建一个 Handler 对象处理连接完成后的后续业务处理。
如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应。
Handler 会完成 read -> 业务处理 -> send 的完整业务流程。
注:Redis 6.0 以下版本,属于单 Reactor 单线程模式。
优点:模型简单,没有多线程,进程通信,竞争的问题,全部都在一个线程中完成。
缺点:
单线程无法利用多核。
处理请求发生耗时,会阻塞整个线程,影响整体性能。
并发请求过高,读取/写回数据存在瓶颈。
2)、单 Reactor 多线程
单 Reactor 多线程也是只有一个 Reactor,与单线程的主要区别在于,使用多线程来进行业务逻辑的处理。
一个线程 + 一个线程池:
单线程:建立连接(Acceptor)和 监听accept、read、write事件(Reactor),复用一个线程。
工作线程池:处理事件(Handler),由一个工作线程池来执行业务逻辑,包括数据就绪后,用户态的数据读写。
具体流程:
主线程中,Reactor 对象通过 select 监听连接事件,收到事件后通过 dispatch 进行分发。
如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件。
如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler 来进行相应。
Handler 只负责响应事件,不进行业务处理,Handler 通过 read 读取到数据后,会发给 Processor 进行业务处理。
Processor 会在独立的子线程中完成真正的业务处理,然后将响应结果发给主进程的 Handler 处理,Handler 收到响应后通过 send 将响应结果返回给 client。
优点:能够充分利用多核多 CPU的处理能力
缺点:
多线程数据共享和访问比较复杂
Reactor 承担所有事件的监听和响应,只在主线程中运行,瞬间高并发时会成为性能瓶颈
3)、多 Reactor 多线程/进程
相比多线程 Reactor 模型,多 reactor 多线程模型拥有了一个独立处理 SocketChannel 连接的线程池,当客户端从 Acceptor 建立连接之后,便将该连接绑定到 Subreactor 线程池中的某个线程中,然后由该线程绑定客户端感兴趣的I/O事件(READ/WRITE),监听客户端连接请求,最后处理。
MainReactor : 监听 ServerSocketChannel 、建立与 SocketChannel 的连接、将完成建立连接之后的Socket 交给subReactor。
SubReactor : 监听SocketChannel的 I/O事件,完成编解码、相应的业务处理(默认为CPU个数)。
三个线程池:
主线程池:建立连接(Acceptor),并且将accept事件注册到从线程池。
从线程池:监听accept、read、write事件(Reactor),包括等待数据就绪时,内核态的数据I读写。
工作线程池:处理事件(Handler),由一个工作线程池来执行业务逻辑,包括数据就绪后,用户态的数据读写。
具体流程:
主进程中 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 接收,将新的连接分配给某个子进程。
子进程中的 SubReactor 将 MainReactor 分配的连接加入连接队列进行监听,并创建一个 Handler 用于处理连接的各种事件。
当有新的事件发生时,SubReactor 会调用里连接对应的 Handler 来响应。
Handler 完成 read -> 业务处理 -> send 的完整业务流程。
优点:
主进程和子进程的职责非常明确,主进程只负责接收新连接,子进程负责完成后续的业务处理。
主进程和子进程的交互很简单,主进程只需要把新的连接传递给子进程,子进程无需返回数据。
子进程之间是相互独立的,无需同步共享之类的处理(这里仅限于网络模型相关的 select、read、send等无须同步共享,"业务处理"还是有可能需要同步共享的)。
参考资料
Redis不是一直号称单线程效率也很高吗,为什么又采用多线程了?
Redis 单线程的理解
Redis 网络通信模块源码分析(一)
Redis 多线程网络模型全面揭秘
Redis 的通讯协议及事件处理机制
Redis 单线程事件循环
10 | Redis事件驱动框架(中):Redis实现了Reactor模型吗?(极客时间)
Netty-Reactor模式总结
Java NIO 系列文章之 浅析Reactor模式
推荐
PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!
以上是关于Redis 是单线程的???的主要内容,如果未能解决你的问题,请参考以下文章
《为什么说Redis是单线程的以及Redis为什么这么快!》