Reactor 典型的 NIO 编程模型
Doug Lea 在 Scalable IO in Java 的 PPT 中描述了 Reactor 编程模型的思想,大部分 NIO 框架和一些中间件的 NIO 编程都与它一样或是它的变体。本文结合 PPT 按照自己的理解整理而来,最终编写了一个简单的 NIO 回显服务。
Reactor 之所以高效是因为采用了分而治之和事件驱动的设计。大部分网络服务像 Web 服务器、分布式对象的通信等大多数具有相同的基本处理流程:
- 读取请求数据 - read
- 按协议解析请求 - decode
- 业务处理 - process
- 按协议编码内容生成响应 - encode
- 发送响应 - send
传统的服务器设计是:一连接一处理线程,也就是我们常说的 BIO 编程。BIO 在读取和发送时是阻塞的,在请求的整个生命周期内,不管有没有数据可读或待发送,都绑定和占用了这个处理线程。
传统模型中,线程的处理单元是一次完整的请求,为了把线程解放出来,Reactor 对这个处理单元进行了分解。
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 对其定义如下:
Reactor 是一个或多个输入事件的处理模式,用于处理并发传递给服务处理程序的服务请求。服务处理程序判断传入请求发生的事件,并将它们同步的分派给关联的请求处理程序。
Reactor 模式按照职责不同,通常可以把线程分为 Reactor 线程、IO 线程和业务线程:
- Reactor 线程:轮询通知发生IO的通道,并分派合适的 Handler 处理
- IO 线程:执行实际的读写操作
- 业务线程:执行应用程序的业务逻辑
PPT 中介绍了三种版本的实现。
3.1 单线程版本

单线程版本就是用一个线程完成事件的通知、实际的 I/O 操作和业务处理。Reactor 作用就是要迅速的触发 Handler ,显然 Handler 处理的过程会导致 Reactor 变慢,此时可以将 非 IO 操作从 Reactor 线程中分离。
3.2 多线程版本

多线程版本将业务处理和 I/O 操作进行分离,Reactor 线程只关注事件分发和实际的 IO 操作,业务处理如协议的编解码都分配给线程池处理。可能会有这样的情况发生,业务处理很快,大部分的 Reactor 线程都在处理 IO,导致 CPU 闲置,降低了响应速度。
3.3 主从 Reactor 版本

主从 Reactor 版本设计了一个 主Reactor 用于处理连接接收事件,多个 从Reactor 处理实际的 I/O,分工合作,匹配 CPU 和 IO 速率。
3.4 一个变体
以上的三个版本中,Reactor 线程都参与了 IO 操作,有一种变体是把实际的 IO 操作也转移到线程池线程中,这样从数据的读取到业务的处理都是由一个线程完成,可以避免锁的竞争。
小结
知易行难,为了更好的理解,这里对 PPT 中介绍的3个版本实现了一个简单的回显服务,内部有比较详细的代码注释。
源码地址:https://github.com/tonwu/reactor
Reactor 典型的 NIO 编程模型的更多相关文章
- Nio编程模型总结
终于,这两天的考试熬过去了, 兴致冲冲的来整理笔记来, 这篇博客是我近几天的NIO印象笔记汇总,记录了对Selector及Selector的重要参数的理解,对Channel的理解,常见的Channel ...
- NIO 编程模型
NIO 编程模型 Doug Lea 在 Scalable IO in Java 的 PPT 中描述了 Reactor 编程模型的思想,大部分 NIO 框架和一些中间件的 NIO 编程都与它一样或是它的 ...
- 手动搭建I/O网络通信框架3:NIO编程模型,升级改造聊天室
第一章:手动搭建I/O网络通信框架1:Socket和ServerSocket入门实战,实现单聊 第二章:手动搭建I/O网络通信框架2:BIO编程模型实现群聊 在第二章中用BIO编程模型,简单的实现了一 ...
- Java网络编程和NIO详解3:IO模型与Java网络编程模型
Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32 ...
- 谈谈传统BIO网络编程模型的局限性与NIO
先来看看我们的server端: 创建一个serversocket,进行监听,每来一个客户端,就启动一个新启动为其服务: private void createListenSocket() { //如果 ...
- NIO&AIO编程模型
NIO线程模型 什么是NIO线程模型? 上图是NIO的线程模型, 基于select实现, 这种线程模型的特点: 多条channel通过一个选择器和单挑线程绑定, 并且在这种编程模型中, Cha ...
- Linux IO模型和网络编程模型
术语概念描述: IO有内存IO.网络IO和磁盘IO三种,通常我们说的IO指的是后两者. 阻塞和非阻塞,是函数/方法的实现方式,即在数据就绪之前是立刻返回还是等待. 以文件IO为例,一个IO读过程是文件 ...
- 手动搭建I/O网络通信框架4:AIO编程模型,聊天室终极改造
第一章:手动搭建I/O网络通信框架1:Socket和ServerSocket入门实战,实现单聊 第二章:手动搭建I/O网络通信框架2:BIO编程模型实现群聊 第三章:手动搭建I/O网络通信框架3:NI ...
- Reactor 模型(一)基本并发编程模型
Reactor 模型(一)基本并发编程模型 Netty 系列目录 (https://www.cnblogs.com/binarylei/p/10117436.html) 在讲解 Reactor 线程模 ...
随机推荐
- GIT的使用流程
GIT的使用流程 1 github注册流程 1 进入github官网:https://github.com/ 2 注册一个自己的github账号 3 右上角选择New repository 4 进入c ...
- Android--性能测试关注的指标
性能测试过程中,出现的一些问题可直接导致了用户对当前app的使用率和卸载率,如果app使用时卡顿严重或者加载页面慢,cpu占用率高,导致app闪退等问题,在测试过程中,则需特别关注性能方面的体验,ap ...
- SpringBoot(二)_项目属性配置
修改端口 在main/resources/application.properties修改端口 server.port=8088 此时启动访问localhost:8088/hello 就会看到 Hel ...
- keras实现简单性别识别(二分类问题)
keras实现简单性别识别(二分类问题) 第一步:准备好需要的库 tensorflow 1.4.0 h5py 2.7.0 hdf5 1.8.15.1 Keras 2.0.8 opencv-p ...
- 小程序 表单 获取 formId
微信小程序使用模板消息需要使用支付prepay_id或表单提交formId, 要获得 formId 需要在 form 标签中声明属性 report-submit="true" ...
- Haskell学习-函数式编程初探
原文地址:Haskell学习-函数式编程初探 为什么要学习函数式编程?为什么要学习Haskell? .net到前端,C#和JavaScript对我来说如果谈不上精通,最起码也算是到了非常熟悉的 ...
- Python中最好用的命令行解析工具:argparse
Python 做为一个脚本语言,可以很方便地写各种工具.当你在服务端要运行一个工具或服务时,输入参数似乎是一种硬需(当然你也可以通过配置文件来实现). 如果要以命令行执行,那你需要解析一个命令行参数解 ...
- C# 语音合成
1. 引用System.Speech 2. 通过SpeechSynthesizer类朗读文本 new SpeechSynthesizer().SpeakAsync("我们都是好孩子We're ...
- 关于thinkphp5手动抛出Http异常时自定义404页面报错的问题
在使用HttpException手动抛出异常时,希望跳转到自定义的错误页面,官方的文章中是这样描述的. 可以使用\think\exception\HttpException类来抛出异常 // 抛出 H ...
- fab 菜单实现—圆形、半圆、扇形、直线、射线
前段时间记录一下fab 菜单实现之前传-钟表表盘,今天终于弄正文了. 本文基于上篇文章的布局方式和位置计算,并参考35 Cool Floating Action Button Animations(h ...