翻译一下Doug Lea所写的Scalable IO in Java(Java可伸缩IO)
大多数的网络服务有着同样的处理流程:
- Read request 请求数据读取
- Decode request 请求数据解码
- Process service 业务处理
- Encode reply 响应编码
- Send reply 响应发送
但是每一步的本质和代价是不一样的。
经典的服务器端套接字循环实现,每来一个客户端启动一个新的线程处理,但是这种方式第一个问题是无限的创建线程,第二个问题是IO阻塞。
可伸缩性目标
负载增加时优雅的降级(更多客户端)
资源(CPU,内存,磁盘,带宽)增加时持续提高
面临可用性和性能目标
更少的延时
满足高峰需求
服务质量可调
分治法是通常实现任何可伸缩性目标的最佳方法。
分治法
分成小任务处理
每个任务执行一个非阻塞的操作
当每个任务就绪的时候执行
这里的IO事件通常作为触发器
在java.nio的基本机制
非阻塞的读和写
任务的分发与感知IO事件相关联
无尽可能的变种
基于事件驱动的家族
事件驱动设计
通常比其他的更高效
更少的资源
通常不需要一个客户端一个线程
更少的开销
更少的上下文切换,更少的锁
但是更慢的分发
必须手工的绑定行为到事件
通常编程更困难
必须分成非阻塞的行为
类似于GUI事件驱动行为
必须清除所有的阻塞:GC,缺页等
必须保持跟踪服务的逻辑状态
背景:AWT中的事件
事件驱动的IO使用相似的想法但是设计上不同
Reactor模式
Reactor(反应器) 通过分发给合适的处理器来响应IO事件,类似于AWT线程
Handlers(处理器) 执行非阻塞的行为,类似与AWT的行为监听器
通过给事件绑定处理器,类似与AWT中添加行为监听器
单线程版本的基本反应器设计
java.nio支持
Channels (通道)
连接文件、套接字等,支持非阻塞读
Buffers(缓冲区)
类数组对象能被通道直接读或写
Selectors(选择器)
告诉通道的哪些集合有了IO事件
SelectionKeys(选择键)
维护IO事件的状态和绑定关系
多线程的设计
有策略的为可伸缩性添加线程
主要适用于多处理器
工作线程
反应器应当快速触发处理器
处理器的处理拖慢了反应器
将非IO处理卸载到其他线程
多反应器线程
反应器线程可以饱和IO
将负载分配给其他反应器
匹配CPU和IO的负载均衡
工作线程
卸载非IO处理加速Reactor线程
类似于POSA2 Proactor设计
比将计算绑定处理重做为事件驱动形式更简单
应当入仍然单纯非阻塞计算
足够处理超负载
但是更难并行处理IO
最好第一次将所有输入读进一个缓冲区
使用线程池调节和控制
通常比客户端需要更少的线程
协调任务
Handoffs(速传)
每个任务就绪,触发或调用下一个
通常更快但是可能不友好
Callbacks(回调)
状态集合,附件等
一个GoF中介者模式的变种
Queues (队列)
例如,通过阶段跨过缓冲区
Futures(未来结果)
每一个任务产生一个结果
协调底层之上的join或者wait/notify
PooledExecutor 的使用
一个可控制的工作线程池
主函数execue(Runnable r)
控制:
任务队列的类型(任何通道)
最大数量的线程
最小数量的线程
热点对战按需线程
保活间隔直到空闲线程死亡
如果需要后面可以被新的一个取代
饱和政策
阻塞,丢弃,生产者运行等
多 Reactor线程
使用Reactor池
用于匹配CPU和IO速率
静态或动态构建
每个有自己的Selector(选择器),Thread(线程),dispatch loop(分发循环)
Main acceptor分配给其他Reactor