Reactor模式

介绍

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单线程

处理流程

  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)部分采用了多线程(线程池)。

处理流程

  1. Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 dispatch 进行分发。
  2. 如果是建立连接请求事件,则由 Acceptoracceptor 处理连接请求,然后创建一个 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多线程

将Reactor分成两部分:

  • mainReactor 负责监听客户端请求,处理新连接的建立,将建立好的 socketChannel 注册给 subReactor

  • subReactor 负责读写数据,业务处理操作。
    通常,subReactor个数上可与CPU个数等同。

Nginx、Memcached和Netty都是采用这种实现。

处理流程

  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