Reactor模式 作者:马育民 • 2023-02-06 09:52 • 阅读:10079 # 介绍 Reactor是一种事件处理的设计模式,经常用于高并发的服务端网络开发中。 无论是C++还是Java编写的网络框架,大多数都是基于Reactor模型进行设计和开发,Reactor模型基于事件驱动,特别适合处理海量的I/O事件。 # 多线程网络编程的缺点 [](/upload/0/0/1IX4uip2dZEQ.png) 传统 socket的接收请求是 **阻塞** 的,需要处理完一个请求才能处理下一个请求,所以在面对高并发的服务请求时,性能就会很差。 所以需要使用多线程,**接收到一个请求,就创建一个线程处理**,这样就 **不会阻塞** 了。 **优点:**的确可以提升性能 **缺点:**但是当 **请求很多** 的时候,就会 **创建大量的线程**,维护线程需要资源的消耗(JAVA中每个线程都需要占用内存),线程之间的切换也需要消耗性能。而且系统创建线程的数量也是有限的,所以当高并发时,会直接把系统拖垮。 ## 分析 用Reactor模式解决 # Reactor模式介绍 ## 三个角色 - Reactor:负责响应事件,将事件分发到绑定了对应事件的Handler,如果是连接事件,则分发到Acceptor。 - Handler:事件处理器。负责执行对应事件对应的业务逻辑。 - Acceptor:绑定了 connect 事件,当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。 ## 三种实现 根据 Reactor 和 线程 的数量不同,分以下3种实现: - 单Reactor单线程 - 单Reactor单线程 - 主从Reactor多线程模型 # 单Reactor单线程 [](/upload/0/0/1IX4ujSEpG2n.png) ### 处理流程 [](/upload/0/0/1IX4ujyLdWy2.jpg) 1. `Reactor` 对象通过 `select` 监控连接事件,收到事件后通过`dispatch` 进行转发。 2. 如果是连接建立的事件,则由 `acceptor` 接受连接,并创建`Handler`(用于处理后续事件)。 3. 如果不是建立连接事件,则 `dispatch` 分发调用 `Handler` 来响应。 4. handler会完成 `read->业务处理->send` 的业务流程。 ### 特点 只有一个 `select` 循环接收请求,客户端 `client` 注册进来,由 `Reactor` 接收注册事件,并 `dispatch` 分发出去,由 `Handler`处理。 ### 理解 一个餐厅里只有一个服务员( `Reactor` ,既是前台也是服务员),负责接待客人(`client`),也负责把客人(`client`)点的菜(`数据`)下达( `dispatch` )给厨师( `handler` ) ### 优点 模型简单,没有多线程、进程通信和竞争的问题,全部都在 **一个线程(红框部分)**中完成。 ### 缺点 - 性能问题,只有一个线程,无法发挥多核CPU的性能,Handler在处理某个连接业务时,就无法处理其他连接事件,很容易导致性能瓶颈。 - 可靠性问题,线程意外终止或进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。 这种模式仅仅只能处理Handler比较快速完成的场景。 # 单Reactor多线程 该模型在事件处理器(Handler)部分采用了多线程(线程池)。 [](/upload/0/0/1IX4ujrn6isT.png) ### 处理流程 [](/upload/0/0/1IX4ujxgzX5z.jpg) 1. `Reactor` 对象通过 `Select` 监控客户端请求事件,收到事件后通过 `dispatch` 进行分发。 2. 如果是建立连接请求事件,则由 `Acceptor` 的 `acceptor` 处理连接请求,然后创建一个 `Handler` 对象处理连接完成后续的各种事件。 3. 如果不是建立连接事件,则 `dispatch` 分发调用连接对应的 `Handler` 来响应。 4. `Handler` 只负责响应事件,不做具体业务处理,通过 `Read` 读取数据后,会分发给后面的 `Worker` 线程池进行业务处理。 5. `Worker` 线程池会分配独立的线程完成真正的业务处理,如何将响应结果发给 `Handler` 进行处理。 6. `Handler` 收到响应结果后通过 `send` 将响应结果返回给`Client`。 ### 理解 相当于餐厅里有一个前台 (`Acceptor`),多个服务员(`多线程`)。前台 (`Acceptor`)只负责接待客人(`client`),服务员(`多线程`)只负责服务客人(`client`)。 ### 优点 使用多线程,可以充分利用多核cpu的处理能力,性能高 对比 **单线程Reactor模型**,**多线程Reactor模式** 在 `Handler` 读写处理时,交给 **工作线程池处理**,不会导致`Reactor` 阻塞,因为 `Reactor` 分发和 `Handler` 处理是分开的,能充分地利用资源。从而提升应用的性能。 ### 缺点 - Reactor只在主线程中运行,承担所有事件的监听和响应,如果短时间的高并发场景下,依然会造成性能瓶颈。 - 多线程数据共享和访问比较复杂。如果子线程完成业务处理后,把结果传递给主线程 `Reactor` 进行发送,就会涉及共享数据的互斥和保护机制。 # 主从Reactor多线程 [](/upload/0/0/1IX4ukCFYNrt.png) 将Reactor分成两部分: - `mainReactor` 负责监听客户端请求,处理新连接的建立,将建立好的 `socketChannel` 注册给 `subReactor`。 - `subReactor` 负责读写数据,业务处理操作。 通常,subReactor个数上可与CPU个数等同。 Nginx、Memcached和Netty都是采用这种实现。 ### 处理流程 [](/upload/0/0/1IX4ukKy2I0n.jpg) 1. `Reactor` 主线程 `MainReactor` 对象通过 `select` 监听连接事件,收到事件后,通过 `Acceptor` 处理连接事件。 2. 当 `Acceptor` 处理连接事件后,`MainReactor` 将连接分配给SubAcceptor。 3. `SubAcceptor` 将连接加入到连接队列进行监听,并创建`Handler` 进行各种事件处理。 4. 当有新事件发生时,`SubAcceptor` 就会调用对应的 `handler` 进行各种事件处理。 5. `handler` 通过read读取数据,分发给后面的 `work` 线程处理。 6. `work` 线程池分配独立的 `work` 线程进行业务处理,并返回结果。 7. `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 原文出处:http://malaoshi.top/show_1IX4ukPvVFlC.html