欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

高性能 IO 设计 Reactor 模式

程序员文章站 2022-03-07 19:16:55
...
传统的服务器设计是:一连接一处理线程,也就是我们常说的 BIO 编程。BIO 在读取和发送时是阻塞的,在请求的整个生命周期内,不管有没有数据可读或待发送,都绑定和占用了这个处理线程。

BIO 多线程并发模式,一个连接一个线程的优点是:
  一定程度上极大地提高了服务器的吞吐量,因为之前的请求在read阻塞以后,不会影响到后续的请求,因为他们在不同的线程中。这也是为什么通常会讲“一个线程只能对应一个socket”的原因。另外有个问题,如果一个线程中对应多个socket连接不行吗?语法上确实可以,但是实际上没有用,每一个socket都是阻塞的,所以在一个线程里只能处理一个socket,就算accept了多个也没用,前一个socket被阻塞了,后面的是无法被执行到的。

BIO 多线程并发模式,一个连接一个线程的缺点是:
  缺点在于资源要求太高,系统中创建线程是需要比较高的系统资源的,如果连接数太高,系统无法承受,而且,线程的反复创建-销毁也需要代价。

改进方法是:

  采用基于事件驱动的设计,当有事件触发时,才会调用处理器进行数据处理。使用Reactor模式,对线程的数量进行控制,一个线程处理大量的事件。


传统模型中,线程的处理单元是一次完整的请求,为了把线程解放出来,Reactor 对这个处理单元进行了分解。


Reactor 之所以高效是因为采用了分而治之和事件驱动的设计。大部分网络服务像 Web 服务器、分布式对象的通信等大多数具有相同的基本处理流程:

读取请求数据 - read
按协议解析请求 - decode
业务处理 - process
按协议编码内容生成响应 - encode
发送响应 - send


1. 分而治之
Reactor 模式将处理过程分为多个小任务,每个任务执行一个非阻塞的操作,任务通常由一个 IO 事件触发执行。这种机制在 java.nio 中提供了支持:

非阻塞的读和写
调度与发生的 IO 事件关联的任务

BIO 线程是以 read->decode->process->encode->send 的顺序串行处理

NIO 将其分成了三个执行单元:读取、业务处理和发送,处理过程如下:
读取:如果无数据可读,线程返回线程池;发生读IO事件,申请一个线程处理读取,读取结束后处理业务

业务处理:线程同步处理完业务后,生成响应内容并编码,返回线程池

发送:发生写IO事件,申请一个线程进行发送

可以看出一个明显的区别就是,一次请求的处理过程是由多个不同的线程完成的,感觉和指令的串行执行和并行执行有点类似。

分而治之的关键在于非阻塞,这样就能充分利用线程,压榨 CPU,提高系统的吞吐能力。

2. 事件驱动
通常比其他模型更高效,它使用的资源更少,不用针对每个请求启用一条线程,减少了上下文切换,减少阻塞。但任务调度可能会慢,必须手动将事件和处理动作绑定。

通常编程比较困难,它必须为服务设计多个逻辑状态,以便跟踪和中断恢复,这也是在非阻塞编程中有大量状态机运用的原因。

NIO 中总共设计了 4 种事件,每个事件发生都会调度关联的任务,分别是:

OP_ACCEPT: 服务端监听到了一个连接,准备接收
OP_CONNECT: 客户端与服务器连接建立成功
OP_READ: 读事件就绪,通道有数据可读
OP_WRITE: 写事件就绪,可以向通道写入数据

java.nio 对事件驱动也提供了支持:
Channels: 连接到文件、Socket 等,支持非阻塞读取
Buffers: 类似数组的对象,可由通道直接读取或写入
Selectors: 通知哪组通道有 IO 事件
SelectionKeys: 维护 IO 事件的状态和绑定信息


3. Reactor 模式
Reactor 是一种设计模式,wikipedia 对其定义如下:

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.


Reactor 是一个或多个输入事件的处理模式,用于处理并发传递给服务处理程序的服务请求。服务处理程序判断传入请求发生的事件,并将它们同步的分派给关联的请求处理程序。

可以看出以下关键点 :

1)事件驱动(event handling)

2)可以处理一个或多个输入源(one or more inputs)

3)通过Service Handler同步的将输入事件(Event)采用多路复用分发给相应的Request Handler(多个)处理


Reactor 模式按照职责不同,通常可以把线程分为 Reactor 线程、IO 线程和业务线程:

Reactor 线程:轮询通知发生IO的通道,并分派合适的 Handler 处理
IO 线程:执行实际的读写操作
业务线程:执行应用程序的业务逻辑


自POSA2 中的关于Reactor Pattern 介绍中,我们了解了Reactor 的处理方式:

1)同步的等待多个事件源到达(采用select()实现)

2)将事件多路分解以及分配相应的事件服务进行处理,这个分派采用server集中处理(dispatch)

3)分解的事件以及对应的事件服务应用从分派服务中分离出去(handler)


基于Reactor Pattern 处理模式中,定义以下三种角色:

1)Reactor 将I/O事件分派给对应的Handler

2)Acceptor 处理客户端新连接,并分派请求到处理器链中

3)Handlers 执行非阻塞读/写 任务










相关标签: NIO