IO 线程模型概述
目前的 IO 线程解决模型个别能够分为以下三类
- 单线程阻塞 I / O 服务模型
- 多线程阻塞 IO 服务模型
- Reactor 模式
依据 Reactor 的数量和解决资源池线程的数量不同,Reactor 模式有如下 3 种典型的实现
- 单 Reactor 单线程
- 单 Reactor 多线程
- 主从 Reactor 多线程
传统阻塞 I / O 线程模型
传统阻塞的 IO 线程线程模型在解决 IO 事件的时候其实就是一直应用一个循环监听端口是否有新的套接字连贯,如果有就进行相应的解决,然而在业务逻辑较为简单的状况下,无奈疾速返回进行新的连贯操作,会导致后续的连贯阻塞,效率太低。其线程模型如下图所示:
(对于多线程阻塞 IO 这里就不再赘述,就是一个线程对应一个连贯)
Reactor 模型
针对传统阻塞 I / O 服务模型的毛病,解决方案个别有两个
- 基于 I/O 复用模型:多个连贯共用一个阻塞对象,应用程序只须要在一个阻塞对象期待,无需阻塞期待所有连贯。当某个连贯有新的数据能够解决时,操作系统告诉应用程序,线程从阻塞状态返回,开始进行业务解决。
- 基于线程池复用线程资源:不用再为每个连贯创立线程,将连贯实现后的业务解决任务分配给线程进行解决,一个线程能够解决多个连贯的业务。
I/ O 复用联合线程池,就是 Reactor 模式根本设计思维,如图所示:
单 Reactor 单线程模式
长处:模型简略,没有多线程、过程通信、竞争的问题,全副都在一个线程中实现
毛病:
- 性能问题,只有一个线程,无奈齐全施展多核 CPU 的性能。Handler 在解决某个连贯上的业务时,整个过程无奈解决其余连贯事件,很容易导致性能瓶颈。
- 可靠性问题,线程意外终止,或者进入死循环,会导致整个零碎通信模块不可用,不能接管和解决内部音讯,造成节点故障
应用场景:客户端的数量无限,业务解决十分疾速,比方 Redis 在业务解决的工夫复杂度 O(1) 的状况
单 Reactor 多线程模型
长处:能够充沛的利用多核 cpu 的解决能力
毛病:多线程数据共享和拜访比较复杂,reactor 解决所有的事件的监听和响应,在单线程运行,在高并发场景容易呈现性能瓶颈.
主从 Reactor 多线程模型
长处:父线程与子线程的数据交互简略职责明确,父线程只须要接管新连贯,子线程实现后续的业务解决。Reactor 主线程只须要把新连贯传给子线程,子线程无需返回数据。
毛病:编程复杂度较高
联合实例:这种模型在许多我的项目中宽泛应用,包含 Nginx 主从 Reactor 多过程模型,Memcached 主从多线程,Netty 主从多线程模型也进行了反对
Netty 中的 reactor 模型
Netty 次要是基于主从 Reactor 多线程模式做了肯定的改良,其中主从 Reactor 都由单线程一个变成了多线程。
Server 端蕴含 1 个 Boss 的 NioEventLoopGroup 和 1 个 Worker 的 NioEventLoopGroup。NioEventLoopGroup 相当于 1 个事件循环组,这个组里蕴含多个事件循环 NioEventLoop,每个 NioEventLoop 蕴含 1 个 Selector 和 1 个事件循环线程。每个 Boss 的 NioEventLoop 循环执行的工作蕴含 3 步:
- 轮询 Accept 事件;
- 解决 Accept I/O 事件,与 Client 建设连贯,生成 NioSocketChannel,并将 NioSocketChannel 注册到某个 Worker NioEventLoop 的 Selector 上;
- 解决工作队列中的工作,runAllTasks。工作队列中的工作包含用户调用 eventloop.execute 或 schedule 执行的工作,或者其余线程提交到该 eventloop 的工作。
每个 Worker NioEventLoop 循环执行的工作蕴含 3 步:
- 轮询 Read、Write 事件;
- 解决 I/O 事件,即 Read、Write 事件,在 NioSocketChannel 可读、可写事件产生时进行解决;
- 解决工作队列中的工作,runAllTasks。
其中工作队列中的 Task 有 3 种典型应用场景:
1、用户程序自定义的一般工作:
ctx.channel().eventLoop().execute(newRunnable() {
@Override
public void run() {// 执行的工作逻辑}
});
2、非以后 Reactor 线程调用 Channel 的各种办法
例如在推送零碎的业务线程外面,依据用户的标识,找到对应的 Channel 援用,而后调用 Write 类办法向该用户推送音讯,就会进入到这种场景。最终的 Write 会提交到工作队列中后被异步生产。
3、用户自定义定时工作
ctx.channel().eventLoop().schedule(newRunnable() {
@Override
public void run() {// 执行的工作逻辑}
}, 60, TimeUnit.SECONDS);