文章目录
- Redis到底是不是单线程?
- 标准Reactor线程模型
- 单reactor单线程
- 单reactor多线程
- 多reactor多线程
- redis6.0 之前的单线程模型
- redis6.0 之后的单线程模型
- 为什么redis最初选择的单线程网络模型?
- 为什么redis6.0 io读写要用多线程?
- Redis 6.0和Memcached多线程模型对比
- 总结
- 3种标准reactor模型的灵活应用
- redis6.0 的reactor应用
- dubbo的reactor应用
Redis到底是不是单线程?
我们都听说过redis是单线程的,但这么说并不准确。确切的说在redis4.0版本之前,redis是单线程的。
在redis 4.0为了防止耗时的命令阻塞线程,导致无法处理后续事件。引入了多线程来处理一些非阻塞命令。有:UNLINK、FLUSHALL ASYNC、FLUSHDB ASYNC等。备份aof、集群通信等模块是单独线程的。但是整个网络模型依然是单线程的,所以我们称之为单线程。
redis 6.0 就真正的在网络模型上加入多线程IO来解决网络IO的性能瓶颈。
此时IO读写是多线程的,执行命令依旧是单线程的。
标准Reactor线程模型
标准的reactor线程模型有3种:单reactor单线程、单reactor多线程、多reactor多线程。
单reactor单线程
Redis的单线程模型就是使用的经典的单线程Reactor模型。
我们先看看单线程的Reactor模型
消息处理流程:
Reactor对象通过select/poll/epoll等IO多路复用监控连接事件,收到事件后通过dispatcher事件分发器进行转发。
如果是连接建立的事件,则由acceptor接受连接,并创建Handler处理后续事件。
如果不是建立连接事件,则Reactor会分发调用Handler来响应。
Handler会完成read->业务处理->send的完整业务流程。
优点:
单线程运行,串行操作,不需要加锁,逻辑简单。
缺点:
仅用一个线程处理请求,对于多核资源机器来说是有点浪费的。
当处理读写任务的线程负载比较重,将会阻塞后续的事件处理,导致整体延迟变大。
应用:
Redis网络模型。(6.0版本以前)
单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 承担所有事件的监听和响应,只在主线程中运行,瞬间高并发时会成为性能瓶颈
多reactor多线程
比起单线程模型,它是将Reactor分成两部分:
mainReactor 负责监听server socket,用来处理网络IO连接建立操作,将建立的socketChannel指定注册给subReactor。 (只负责监听)
subReactor 主要做和建立起来的socket做数据交互和事件业务处理操作。通常,subReactor个数上可与CPU个数等同。一般是多个,这样的话,就可以充分利用多核的优势。 (负责IO读写和命令的执行)
区别于单线程Reactor模式,这种模式不再是单线程的事件循环,而是有多个线程subReactors各自维护一个独立的事件循环,由 mainReactor 负责接收新连接并分发给 subReactors 去独立处理,最后 subReactors 回写响应给客户端。
优点:
响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
可扩展性,可以方便地通过增加Reactor实例个数来充分利用CPU资源;
缺点:
如果多个线程可能操作同一份数据,就涉及到底层数据同步的问题,则必然会引入某些同步机制,比如锁。增加了代码复杂度,同时增加了同步机制的开销。
应用:
Nginx, Netty, Swoole, Memcached就是使用的这个模型
redis6.0 之前的单线程模型
redis的网络事件处理器是基于Reactor模式,又叫做文件事件处理器。
文件事件处理器使用I/O多路复用来同时监听多个套接字,并根据套接字执行的任务关联到不同的事件处理器。
文件事件以单线程方式运行,但通过使用I/O多路复用程序来监听多个套接字,文件事件处理器实现了高性能的网络通信模型。
Redis 在处理客户端的请求时,包括接收(socket读)、解析、执行、发送(socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的单线程。
其实就是应用的reactor单线程模型,从头到尾只有主线程处理io的链接、读写、业务执行。
redis6.0 之后的单线程模型
redis6.0之后的单线程模型,在负责io读写时使用了sub reactor多线程,在具体业务执行命令时还是返回由主线程串行执行。
为什么redis最初选择的单线程网络模型?
Redis 最初选择单线程网络模型的理由是:CPU 通常不会成为性能瓶颈,瓶颈往往是内存和网络,我们平时用多线程处理的业务一般比较耗时,比如业务的io读写、数据库操作,这是cpu是在闲着的,为了提高cpu利用率,我们才用多线程,但是redis本身就是直接操作的内存,io读写操作是很快的,cpu利用率很高,因此单线程足够了。
为什么redis6.0 io读写要用多线程?
CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存大小和网络带宽。
从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分时间,瓶颈主要在于网络的 IO 消耗, 所以选择多线程IO来实现读写。主线程来执行Redis命令。
如果多线程包括了IO读写,解析和执行的整个过程,那么多线程需要面临线程安全的问题,Redis 6.0版本之前是没有考虑线程安全的,如果使用多线程来处理命令的执行,需要大量的改动来保证多线程的安全机制,实现更复杂。为了避免了不必要的上下文切换和竞争条件,多线程导致的切换而消耗 CPU,也不用考虑各种锁的问题,就让执行这一步只使用主线程。
总结就是:
将主线程 IO 读写任务拆分出来给一组独立的线程处理,使得多个 socket 读写可以并行化,但是 Redis 命令还是主线程串行执行。
Redis 6.0和Memcached多线程模型对比
相同点:
都采用了 Master-Worker 的线程的模型
不同点:
Memcached 执行主逻辑也是在 Worker 线程里,模型更加简单,实现了真正的线程隔离,通过各种锁机制来保证数据的线程安全。
而 Redis 把执行逻辑交还给 Master 线程,虽然一定程度上增加了模型复杂度,但也解决了数据的线程安全问题。
总结
- redis在4.0之前是纯单线程模型。
- 在4.0之后,aof备份、集群交互等操作由别的线程单独操作,但是网络通信执行命令模块还是单线程,应用的就是标准reactor单线程模型,由单一线程完成的网络连接、网络数据io读写、业务命令执行。
- 在6.0之后,网络通信的连接、业务执行命令还是由主线程完成的,但是期间的网络io数据读写是用了sub reactor多线程的。并不是标准的多reactor多线程模型。
3种标准reactor模型的灵活应用
标准的三种reactor线程模型,从单reactor单线程、单reactor多线程到多reactor多线程,也是在一步一步减少阻塞点的。从单reactor单线程到单reactor多线程减少了业务执行的阻塞点,从单reactor多线程到多reactor多线程减少了网络io读写等待的阻塞点。
实际上,很多中间件并没有完全按照标准的3种reactor模型,而是这3种模式的变种。
redis6.0 的reactor应用
redis6.0就是只在网络io读写时应用了多线程,在业务命令执行时又返回由主线程串行执行,综合应用了单reactor单线程模式和多reactor多线程模式,用多reactor多线程模式中的sub reactor,减少了网络io等待读写的阻塞,同时应用单reactor单线程执行业务命令,避免处理并发,避免加锁。
dubbo的reactor应用
dubbo在业务处理步骤时,也不是直接用sub reactor线程处理的,而是有专门的业务线程池,综合应用了单reactor多线程模式和多reactor多线程模式,用多reactor多线程模式中的sub reactor,减少了网络io等待读写的阻塞,同时应用单reactor多线程模式中的业务线程池,集中处理业务。关键就是用多线程减少阻塞点,灵活应用了3种reactor线程模型