第一次看到这篇文章,是看到上关于wiki上关于Non-blocking-IO词条中提到了这篇文章,在后面学习Netty中大家都提到了这篇文章,就抽时间理解了一下这篇文章的内容。我个人认为改文章以Java NIO api为支持,提供了Reactor模型的实现思路。适合我们在了解简单java nio的api基础上加强与业务实现的联系。
个人觉得代码实现过程的收获点:
TL;DR
论述通过分解问题,找到根本问题,解决问题的路线展开
-> 把网络服务进行拆分,细化问题
-> 传统的网络服务设计
-> 可伸缩的网络服务目标
-> 引出分治法
-> 根据分治思想引出AWT以及比AWT粒度更小的事件驱动思想
-> 引出Reactor模式
-> 简单的Reactor模式满足非阻塞的网络服务实现
-> 引入Worker Thread Pool,通过细化拆解非IO部分的任务提升性能(网络IO操作与业务的拆分)
-> 引入多Reactor线程,利用资源划分,提高Reactor的利用(网络连接与网络IO操作的细化拆分)
内容翻译
大纲
- 伸缩式的网络服务
- 事件驱动处理
- Reactor反应器模式(基础版本、多线程版本、其他变种)
- 浏览java.nio 包内的非阻塞IO api
网络服务
- web 服务,分布式对象,等
- 通用的基础结构
- 读请求
- 解码请求
- 处理服务
- 编码回复
- 发送回复
- 不同点和开销在每一步中
- XML解析,文件传输,web页面生成,计算服务
传统服务设计结构
- 每一事件在自己所属的线程中处理
传统ServerSocket 循环
每个连接在配置的线程中运行
可伸缩的指标
- 持续增长负载下优雅降级
- 资源消耗平缓递增
- 可用性以及性能指标
- 低延迟
- 高吞吐
- 服务质量可调节
- 类似分治法的弹性架构目标
分治法
- 拆分处理为小的任务;每个任务无阻塞的完成
- 执行每个可执行的任务;经常采用触发器的形式
- 运行被javaNio支持
- 无阻塞的读写
- 分发,任务与被感知到的IO时间连接
- 多种扩展可能性
- 面向事件驱动的体系
事件驱动
- 比其他选择的区分点
- 更少的资源依赖:不用经常分配线程给客户端
- 更少的开销:更少的上下问切换,更少的锁操作
- 除了事件分发会更慢:在非常多的活动与事件绑定情况下
- 编程难点
- 需要把处理过程必须分解为简单的无阻塞行为
- 比GUI事件驱动粒度更小
- 不能消除所有阻塞,GC/页缓存等
- 必须维持服务的逻辑状态
- 需要把处理过程必须分解为简单的无阻塞行为
背景介绍:AWT中的事件处理
- IO事件驱动经常使用相似的思路,除了一些设计区别
Reactor Pattern
Reactor
通过分发给适当的处理来响应IO事件;对比AWT线程
Handlers
提供无阻塞操作;对比AWT中的动作监听器
管理与事件绑定处理器
对比:AWT中的#addActionListener()
基础的Reactor设计,单线程模式
java Nio的支持
Channels
支持无阻塞的读取连接文件,socket
Buffers
像数组一样可以被直接读写Channels里
Selectors
告诉那个Channels有IO事件
SelectionKeys
负责IO事件状态和绑定
Reactor 单线程实现
第一步:创建ServerSocket,等待连接
第二步:事件的分发循环
第三步:接受连接
第四步:处理器设定
第五步:请求被处理
不同状态处理器
通过attachment 绑定合适的处理器
Reactor 多线程实现
- 有策略的增加线程为了弹性伸缩;主要适合多处理器
- 工作线程
- Reactors应该快速触发处理器(处理器处理会比Reactor慢)
- 拆解非IO处理过程到其他线程中
- 多Reactor Threads
- Reactor线程可以充分用于IO处理
- 多Reactor之间做负载(合理利用CPU和IO速率)
Worker Threads
- 拆解非IO处理已提高Reactor线程的处理速度
- Reactor线程,比POSA2 Proactor 设计的更小
- 计算绑定处理比转换事件驱动的形式更简单【个人理解:这里表达的是如果使用Worker Threads,方便了handler的实现,如果都通过事件驱动,那么handler的实现必须依照Reactor中的要求。类似于我业务的实现要受网络连接上的处理规则限制】
- 应该保持非阻塞计算(足够的处理消耗大于额外的开销消耗)
- 对于IO的持续处理比较困难,最好是一开始就读取到所有的输入到一个缓冲区内
- 使用线程池可以协调和控制,通常需要更小的线程服务更多设备
Worker Thread Pools
协同任务
- 传递:每个任务,触发,执行下一个;速度快但是容易被破坏
- 回调:分发后的处理器回调,是GOF 调节模式的变种
- 队列:不同阶段访问buffer
- Future 异步通知:当每个任务产生结果,协同层顶部需要同步处理 join或者 wait/notify
使用池化执行器
- 一个可控的工作线程池
- 核心方法 execute(Runnable r)
- 可以控制:
- 任务队列类型
- 最大、最小线程数
- 比守护线程更柔和
- 保活时间直到idle线程挂掉
- 更加丰富的策略
多Reactor线程
- 使用Reactor线程
- 用于匹配CPU和IO速率
- 讲台或者动态的构造器,拥有自己的选择器,线程,分发循环
- 主接收器分配给其他Reactor
根据文章中实现的三种Reactor模式代码地址:
代码传送门