高性能 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 执行非阻塞读/写 任务
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 执行非阻塞读/写 任务