介绍
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






