Reactor 模型

传统IO模型:

PPC 是 Process Per Connection 的缩写,其含义是指每次有新的连接就新建一个进程去专门处理这个连接的请求。

TPC shi Thread Per Connection 的缩写,其含义是每次有新的连接就创建一个线程去处理。

缺点:

资源浪费,每次连接就创建一个线程,结束后线程就销毁了

基于以上缺点,我们可以使用线程池进行线程复用,但是如果某个线程阻塞到read 或者是write 上,那么这个连接就一直不会释放,导致其他线程阻塞在获取线程池连接上。

解决这个问题的最简单的方式是将 read 操作改为非阻塞,然后进程不断地轮询多个连接。这种方式能够解决阻塞的问题,但解决的方式并不优雅。首先,轮询是要消耗 CPU 的;其次,如果一个进程处理几千上万的连接,则轮询的效率是很低的。

为了更好地解决上述问题,我们可以当连接上有数据的时候线程采取处理,也就是使用事件监听的模式,如果有数据,你告诉我,然后我去处理,这就是IO多路复用

IO多路复用

  • 当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接,常见的实现方式有 select、epoll、kqueue 等。

  • 当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理

IO 多路复用结合线程池,完美地解决了 PPC 和 TPC 的问题,而且“大神们”给它取了一个很牛的名字:Reactor,中文是“反应堆”。联想到“核反应堆”,听起来就很吓人,实际上这里的“反应”不是聚变、裂变反应的意思,而是“事件反应”的意思,可以通俗地理解为“来了一个事件我就有相应的反应”,这里的“我”就是 Reactor,具体的反应就是我们写的代码,Reactor 会根据事件类型来调用相应的代码进行处理。Reactor 模式也叫 Dispatcher 模式(在很多开源的系统里面会看到这个名称的类,其实就是实现 Reactor 模式的),更加贴近模式本身的含义,即 I/O 多路复用统一监听事件,收到事件后分配(Dispatch)给某个进程。

Reactor

1 reactor 模式:

  • 单 Reactor 单进程 / 线程。

  • 单 Reactor 多线程。

  • 多 Reactor 多进程 / 线程。

2 单 Reactor 单进程 / 线程

  • reactor 阻塞在select 上,收到时间后通过dispatch进行分发。
  • 如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件。

  • 如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler(第 2 步中创建的 Handler)来进行响应。

  • Handler 会完成 read-> 业务处理 ->send 的完整业务流程。

缺点:无法发挥出多核优势,如果某个handler阻塞了,那么所有的后续handler都处理不了。

优点:无线程切换,无线程通信,无线程竞争

适用场景:适合于业务处理很快的场景

例子:redis 

3 单 Reactor 多线程

相比于单Reactor单进程,handler收到数据并不需要进行业务处理,而是读到数据后,交给processor进行处理。

Processor 会在独立的子线程中完成真正的业务处理,然后将响应结果发给主进程的 Handler 处理;Handler 收到响应后通过 send 将响应结果返回给 client。

缺点:

  • 多线程数据共享和访问比较复杂
  • Reactor 承担所有事件的监听和响应,只在主线程中运行,瞬间高并发时会成为性能瓶颈

优点:充分利用多核CPU的能力

适用场景:连接不是很多,对高性能有要求

例子:java NIO

4 多 Reactor 多进程 / 线程

  • 父进程中 mainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 接收,将新的连接分配给某个子进程。

  • 子进程的 subReactor 将 mainReactor 分配的连接加入连接队列进行监听,并创建一个 Handler 用于处理连接的各种事件。

  • 当有新的事件发生时,subReactor 会调用连接对应的 Handler(即第 2 步中创建的 Handler)来进行响应。

  • Handler 完成 read→业务处理→send 的完整业务流程。

优点:

  • 职责明确,父进程只负责接收新连接,具体是连接还是read不关心。然后将连接交给subReactor,subReactor进行处理
  • 父子Reactor交互简单
  • 子进程之间是独立的

例子:NGINX, Netty,Memcache

nginx实现:master进程不同于一般的主从式reactor(一般的主从式reactor设计会是主reactor负责将连接accept下来,然后再将connection fd挂载到子reactor中),这个master进程的主要任务就是监听信号的,也就是对nginx的一些命令做处理(还记得在之前提到的nginx中的命令处理都是通过信号来处理的吗),然后再将这些处理通过sockerpair()或者信号等方式通知给worker进程,master进程同时监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。同时,这个master进程负责listen这个整个服务器的listen fd,然后worker进程通过竞争accept lock来将连接从全连接队列里取出来,nginx也是通过这个竞争accept lock的方式避免的惊群(多个进程同时等待网络的连接事件,当这个事件发生时,这些进程被同时唤醒,就是“惊群”)现象。

Reactor备注:

processor 和handler说明:可以在同一线程进行处理,也可以在不同的线程进行处理。如果有不同的线程进行处理,那么handler线程我们叫做IO线程,也就是只做read 和send操作,processor叫做work线程,由具体的业务方来进行实现,只进行具体的业务处理。但是分开后,会涉及到线程的上下文切换,具体操作需要根据不同情况衡量。

Proactor

Reactor 是非阻塞同步网路模型,即handler执行read / send这类IO操作是同步的,如果把I/O 操作改为异步就能够进一步提升性能,这就是异步网络模型 Proactor。

Proactor 中文翻译为“前摄器”比较难理解,与其类似的单词是 proactive,含义为“主动的”,因此我们照猫画虎翻译为“主动器”反而更好理解。Reactor 可以理解为“来了事件我通知你,你来处理”,而 Proactor 可以理解为“来了事件我来处理,处理完了我通知你”。这里的“我”就是操作系统内核,“事件”就是有新连接、有数据可读、有数据可写的这些 I/O 事件,“你”就是我们的程序代码。

步骤:

  • Proactor Initiator 负责创建 Proactor 和 Handler,并将 Proactor 和 Handler 都通过 Asynchronous Operation Processor 注册到内核。

  • Asynchronous Operation Processor 负责处理注册请求,并完成 I/O 操作。

  • Asynchronous Operation Processor 完成 I/O 操作后通知 Proactor。

  • Proactor 根据不同的事件类型回调不同的 Handler 进行业务处理。

  • Handler 完成业务处理,Handler 也可以注册新的 Handler 到内核进程。

总结:

1 、PPC ,TPC:  假如我们去饭店点餐,饭店人很多,如果我们付了钱后站在收银台等着饭端上来我们才离开,这就成了同步阻塞了。

2、如果我们付了钱后给你一个号就可以离开,饭好了老板会叫号,你过来取。这就是Reactor模型。

3、如果我们付了钱后给我一个号就可以坐到坐位上该干啥干啥,饭好了老板会把饭端上来送给你。这就是Proactor模型了。

### Java Reactor 模型概述 Java 中的 Reactor 模型是一种用于高效处理 I/O 操作的设计模式,特别适用于高并发场景下的网络编程。该模型的核心思想是通过事件驱动的方式管理输入/输出操作,从而提高系统的响应速度和资源利用率。 #### 主要组件 - **Reactor**: 负责监听并分发各种类型的I/O事件给相应的处理器。 - **Handlers (处理器)**: 接收来自Reactor分配的任务,并执行具体的业务逻辑处理[^1]。 #### 三种常见的 Reactor 实现方式 ##### 单 Reactor 单线程模型 在这种最简单的配置下,整个应用程序运行在一个单独的工作线程上,此线程负责所有的读写以及接受新的连接请求等工作。尽管简单易懂,但在面对大量并发访问时性能会受到极大限制。 ##### 单 Reactor 多线程模型 为了克服单线程带来的局限性,在这种设计里引入了一个专门用来接收新连接的Acceptor线程加上一组工作线程池来异步地完成实际的数据传输任务。这种方式能够在一定程度上缓解CPU瓶颈问题的同时保持较好的吞吐量表现[^2]。 ##### 主从 Reactor 多线程模型 进一步优化后的方案采用了更加复杂的架构—即存在一个主Reactor实例(Boss)仅专注于侦听端口上的入站TCP握手消息;而若干个子Reactor对象(Workers),它们各自拥有独立的选择器用于监控已建立好的通信链路状态变化情况。这样的分工协作机制不仅有助于分散负载压力而且提高了整体稳定性与可扩展性[^3]。 ### Netty 的 Reactor 应用案例 Netty 是一款基于 NIO 构建高性能网络应用框架的成功典范之一,其内部正是运用了上述提到过的第三种改进版 Reactor 设计思路: 当一个新的客户端发起连接请求时,`BossGroup` 将接管这个过程直至成功建立起双向数据交换通道为止;随后再把后续可能发生的任何交互活动交由 `WorkerGroups` 来具体实施。每个 worker 都会在自己的事件循环内等待特定条件触发以便及时作出反应,比如接收到对方发送过来的信息包或是本地准备好了待发出的内容等等[^4]。 ```java EventLoopGroup bossGroup = new NioEventLoopGroup(); // 创建 Boss 线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 创建 Worker 线程组 try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 使用 NIO 类型的服务端 Channel .childHandler(new ChannelInitializer<SocketChannel>() { // 设置管道初始化器 @Override public void initChannel(SocketChannel ch) throws Exception { Pipeline p = ch.pipeline(); p.addLast(new MyHandler()); // 添加自定义 Handler 进行业务处理 } }); // 绑定端口号并启动服务器 ChannelFuture f = b.bind(PORT).sync(); System.out.println("Server started and listening on " + PORT); // 等待服务关闭 f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值