介绍
Reactor是一种事件处理的设计模式,经常用于高并发的服务端网络开发中。
无论是C++还是Java编写的网络框架,大多数都是基于Reactor模型进行设计和开发,Reactor模型基于事件驱动,特别适合处理海量的I/O事件。
多线程网络编程的缺点
传统 socket的接收请求是 阻塞 的,需要处理完一个请求才能处理下一个请求,所以在面对高并发的服务请求时,性能就会很差。
所以需要使用多线程,接收到一个请求,就创建一个线程处理,这样就 不会阻塞 了。
优点:的确可以提升性能
缺点:但是当 请求很多 的时候,就会 创建大量的线程,维护线程需要资源的消耗(JAVA中每个线程都需要占用内存),线程之间的切换也需要消耗性能。而且系统创建线程的数量也是有限的,所以当高并发时,会直接把系统拖垮。
分析
用Reactor模式解决
Reactor模式介绍
三个角色
Reactor:负责响应事件,将事件分发到绑定了对应事件的Handler,如果是连接事件,则分发到Acceptor。
Handler:事件处理器。负责执行对应事件对应的业务逻辑。
Acceptor:绑定了 connect 事件,当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。
三种实现
根据 Reactor 和 线程 的数量不同,分以下3种实现:
- 单Reactor单线程
- 单Reactor单线程
- 主从Reactor多线程模型
单Reactor单线程
处理流程
Reactor
对象通过select
监控连接事件,收到事件后通过dispatch
进行转发。- 如果是连接建立的事件,则由
acceptor
接受连接,并创建Handler
(用于处理后续事件)。 - 如果不是建立连接事件,则
dispatch
分发调用Handler
来响应。 - handler会完成
read->业务处理->send
的业务流程。
特点
只有一个 select
循环接收请求,客户端 client
注册进来,由 Reactor
接收注册事件,并 dispatch
分发出去,由 Handler
处理。
理解
一个餐厅里只有一个服务员( Reactor
,既是前台也是服务员),负责接待客人(client
),也负责把客人(client
)点的菜(数据
)下达( dispatch
)给厨师( handler
)
优点
模型简单,没有多线程、进程通信和竞争的问题,全部都在 一个线程(红框部分)中完成。
缺点
性能问题,只有一个线程,无法发挥多核CPU的性能,Handler在处理某个连接业务时,就无法处理其他连接事件,很容易导致性能瓶颈。
可靠性问题,线程意外终止或进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。
这种模式仅仅只能处理Handler比较快速完成的场景。
单Reactor多线程
该模型在事件处理器(Handler)部分采用了多线程(线程池)。
处理流程
Reactor
对象通过Select
监控客户端请求事件,收到事件后通过dispatch
进行分发。- 如果是建立连接请求事件,则由
Acceptor
的acceptor
处理连接请求,然后创建一个Handler
对象处理连接完成后续的各种事件。 - 如果不是建立连接事件,则
dispatch
分发调用连接对应的Handler
来响应。 Handler
只负责响应事件,不做具体业务处理,通过Read
读取数据后,会分发给后面的Worker
线程池进行业务处理。Worker
线程池会分配独立的线程完成真正的业务处理,如何将响应结果发给Handler
进行处理。Handler
收到响应结果后通过send
将响应结果返回给Client
。
理解
相当于餐厅里有一个前台 (Acceptor
),多个服务员(多线程
)。前台 (Acceptor
)只负责接待客人(client
),服务员(多线程
)只负责服务客人(client
)。
优点
使用多线程,可以充分利用多核cpu的处理能力,性能高
对比 单线程Reactor模型,多线程Reactor模式 在 Handler
读写处理时,交给 工作线程池处理,不会导致Reactor
阻塞,因为 Reactor
分发和 Handler
处理是分开的,能充分地利用资源。从而提升应用的性能。
缺点
Reactor只在主线程中运行,承担所有事件的监听和响应,如果短时间的高并发场景下,依然会造成性能瓶颈。
多线程数据共享和访问比较复杂。如果子线程完成业务处理后,把结果传递给主线程
Reactor
进行发送,就会涉及共享数据的互斥和保护机制。
主从Reactor多线程
将Reactor分成两部分:
mainReactor
负责监听客户端请求,处理新连接的建立,将建立好的socketChannel
注册给subReactor
。subReactor
负责读写数据,业务处理操作。
通常,subReactor个数上可与CPU个数等同。
Nginx、Memcached和Netty都是采用这种实现。
处理流程
Reactor
主线程MainReactor
对象通过select
监听连接事件,收到事件后,通过Acceptor
处理连接事件。当
Acceptor
处理连接事件后,MainReactor
将连接分配给SubAcceptor。SubAcceptor
将连接加入到连接队列进行监听,并创建Handler
进行各种事件处理。当有新事件发生时,
SubAcceptor
就会调用对应的handler
进行各种事件处理。handler
通过read读取数据,分发给后面的work
线程处理。work
线程池分配独立的work
线程进行业务处理,并返回结果。handler
收到响应的结果后,再通过send
返回给client
。
注意: Reactor主线程可以对应多个Reactor子线程,即SubAcceptor。
理解
相当于餐厅里有 多个前台(mainReactor
) 和 多个服务员(subReactor
),前台只负责接待客人(client
),服务员只负责服务客人。
优点
mainReactor
主要是用来处理客户端请求连接建立的操作。subReactor
主要做和建立起来的连接做数据交互和事件业务处理操作,每个 subReactor
一个线程来处理。
这样的模型使得每个模块更加专一,耦合度更低,能支持更高的并发量。
缺点
实现复杂
总结
Reactor模型具有如下的优点:
- 响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
- 编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
- 可扩展性,可以方便地通过增加Reactor实例个数来充分利用CPU资源;
- 可复用性,Reactor模型本身与具体事件处理逻辑无关,具有很高的复用性。
参考:
https://mp.weixin.qq.com/s/vWbbn1qXRFVva8Y9yET18Q
https://cloud.tencent.com/developer/article/1488120
https://zhuanlan.zhihu.com/p/347779760