nio从入门到放弃
- what
- how
- Scalable IO in Java
- Outline 目录
- Network Services 网络服务
- Classic Service Designs 传统服务设计
- Classic ServerSocket Loop 传统阻塞socket循环
- Scalability Goals 弹性扩容目标
- Divide and Conquer 分治法
- Event-driven Designs 事件驱动设计
- Background: Events in AWT 事件驱动模式
- Reactor Pattern 模式
- Basic Reactor Design 传统Reactor设计
- java.nio Support 支持
- Reactor 1: Setup 设置
- Reactor 2: Dispatch Loop 分发循环
- Reactor 3: Acceptor 响应连接
- Reactor 4: Handler setup reactor处理器实现
- Reactor 5: Request handling 业务处理器实现
- Per-State Handlers 每个状态的处理器
- Multithreaded Designs 多线程设计
- Worker Threads 工作线程
- Worker Thread Pools 工作线程池
- Handler with Thread Pool 线程池处理器
- Coordinating Tasks 协调任务
- Using PooledExecutor 使用自定义线程池
- Multiple Reactor Threads 多线程 Reactor
- Using Multiple Reactors 使用多线程 Reactors
- Using other java.nio features 使用nio特性
- Connection-Based Extensions 基于连接扩展
- API Walkthrough api预览 详见jdk
what
什么是 nio
有什么用
为何要用
how
解读翻译文章
http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
Scalable IO in Java
Outline 目录
- 弹性网络服务
- 事件驱动处理进程
- reactor模式
基础版本
多线程版本
其他变种 - java.nio 非阻塞IO api概览
Network Services 网络服务
网络服务s, 分发对象,等等
大部分都有相同架构
读请求 request
解码请求 decode
处理服务 process
响应内容编码 encode
发送响应内容 response
但是不同的组织 每一步耗时
xml解析 文件传输 web页生成 计算服务
Classic Service Designs 传统服务设计
如图
Classic ServerSocket Loop 传统阻塞socket循环
每个处理handler都在自己的私有线程中
案例代码
class Server implements Runnable {
public void run() {
try {
ServerSocket ss = new ServerSocket(PORT);
while (!Thread.interrupted())
new Thread(new Handler(ss.accept())).start();
// or, single-threaded, or a thread pool
} catch (IOException ex) { /* ... */ }
}
static class Handler implements Runnable {
final Socket socket;
Handler(Socket s) { socket = s; }
public void run() {
try {
byte[] input = new byte[MAX_INPUT];
socket.getInputStream().read(input);
byte[] output = process(input);
socket.getOutputStream().write(output);
} catch (IOException ex) { /* ... */ }
}
private byte[] process(byte[] cmd) { /* ... */ }
}
}
Note: most exception handling elided from code examples
Scalability Goals 弹性扩容目标
随着连接数上去时,平缓的降低性能
增加cpu mem disk bandwidth 磁盘带宽时可持续提升性能
可用性和性能目标
- 短延迟
- 高并发支持
- 可调整服务质量
分治法通常是最可行的实现方式
Divide and Conquer 分治法
处理服务分成更小任务
- 每个任务处理要非阻塞
当任务可用需要立即执行
- 一个io事件通常服务像一个触发器?
如图
java.nio支持这个基础机制
非阻塞读写
通过可感知io事件分发任务组织联系
支持无限的修改变化
事件驱动全家桶设计
Event-driven Designs 事件驱动设计
通常比alternatives非传统的更加高效
- 更少的资源
不需要每个线程管理一个连接 - 更少的overhead开销
更少的上下文切换 总是更少的锁 - 但是分发可能更慢
必须手动bind处理actions到事件 - 对于程序通常更艰难
必须迁移到简易的非阻塞机制
就像是gui事件驱动机制
不能清除所有阻塞:GC, page faults
必须保持跟踪服务逻辑状态
Background: Events in AWT 事件驱动模式
如图
事件驱动io使用类似的主意,但是不同的设计方案
Reactor Pattern 模式
- reactor依靠分发关联的handler响应io事件
类似awt线程 - handlers表现非阻塞行为
类似awt ActionListeners - 依靠绑定bind handler到事件 处理
详看 Pattern-Oriented 面向模式设计 POSA2
Richard Stevens 的网络相关书籍也行
Basic Reactor Design 传统Reactor设计
如图
单线程版本
?单线程reactor轮询所有连接监听可用事件后 将任务于线程池消费处理
java.nio Support 支持
- Channels 通道
连接文件、socket等,支持非阻塞读 - Buffers 缓冲区
类数组,能通过Channels直接读写 - Selectors 选择器
告诉我们哪些Channel已经有io事件 就绪 可处理标记列表 - SelectionKeys 选择事件Key
维持IO事件状态和绑定关系
Reactor 1: Setup 设置
class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverSocket;
Reactor(int port) throws IOException {
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind( new InetSocketAddress(port)); //为何不是serverSocket.bind ??
serverSocket.configureBlocking(false);
SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
sk.attach(new Acceptor());
}
/*
Alternatively, use explicit SPI provider:
SelectorProvider p = SelectorProvider.provider();
selector = p.openSelector();
serverSocket = p.openServerSocketChannel();
*/
Reactor 2: Dispatch Loop 分发循环
// class Reactor continued
public void run() { // normally in a new Thread
try {
while (!Thread.interrupted()) {
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext())
dispatch((SelectionKey)(it.next());
selected.clear();
}
} catch (IOException ex) { /* ... */ }
}
void dispatch(SelectionKey k) {
Runnable r = (Runnable)(k.attachment());
if (r != null)
r.run();
}
Reactor 3: Acceptor 响应连接
// class Reactor continued
class Acceptor implements Runnable { // inner
public void run() {
try {
SocketChannel c = serverSocket.accept();
if (c != null)
new Handler(selector, c);
} catch(IOException ex) { /* ... */ }
}
}
}
如图
Reactor 4: Handler setup reactor处理器实现
final class Handler implements Runnable {
final SocketChannel socket;
final SelectionKey sk;
ByteBuffer input = ByteBuffer.allocate(MAXIN);
ByteBuffer output = ByteBuffer.allocate(MAXOUT);
static final int READING = 0, SENDING = 1;
int state = READING;
Handler(Selector sel, SocketChannel c)
throws IOException {
socket = c; c.configureBlocking(false);
// Optionally try first read now
sk = socket.register(sel, 0);
sk.attach(this);
sk.interestOps(SelectionKey.OP_READ);
sel.wakeup();
}
boolean inputIsComplete() { /* ... */ }
boolean outputIsComplete() { /* ... */ }
void process() { /* ... */ }
Reactor 5: Request handling 业务处理器实现
// class Handler continued
public void run() {
try {
if (state == READING) read();
else if (state == SENDING) send();
} catch (IOException ex) { /* ... */ }
}
void read() throws IOException {
socket.read(input);
if (inputIsComplete()) {
process();
state = SENDING;
// Normally also do first write now
sk.interestOps(SelectionKey.OP_WRITE);
}
}
void send() throws IOException {
socket.write(output);
if (outputIsComplete()) sk.cancel();
}
}
Per-State Handlers 每个状态的处理器
简易使用 GoF State-Object 模式
Rebind 重新绑定关系 handler
class Handler { // ...
public void run() { // initial state is reader
socket.read(input);
if (inputIsComplete()) {
process();
sk.attach(new Sender());
sk.interest(SelectionKey.OP_WRITE);
sk.selector().wakeup();
}
}
class Sender implements Runnable {
public void run(){ // ...
socket.write(output);
if (outputIsComplete()) sk.cancel();
}
}
}
Multithreaded Designs 多线程设计
为了弹性扩展适配多处理器cpu策略添加线程
- Worker Threads 工作线程
Reactors 应该很快触发 handler 处理器
handler处理慢 拉慢 reactor
下架 non-IO 处理占用 转移资源到其他线程 - 多 Reactor 线程
Reactor 线程能占满 工作 IO
分发加载其他 reactor
负载应充分使用 cpu 和 io 性能
Worker Threads 工作线程
- 下架 non-IO 处理占用 转移资源到其他线程
类似 POSA2 Proactor designs - 简化重试工作 compute-bound 处理服务 为 事件驱动 form
应该依然少用非块 nonblocking 计算
足够的处理服务超过开销 outweigh overhead - 但是更困难的用 IO 覆盖处理服务
最好能一次就读取所有输入到 buffer - 使用线程池来实现可控和协调tune
一般来说只需要比连接数少很多的县城数
Worker Thread Pools 工作线程池
Handler with Thread Pool 线程池处理器
class Handler implements Runnable {
// uses util.concurrent thread pool
static PooledExecutor pool = new PooledExecutor(...);
static final int PROCESSING = 3;
// ...
synchronized void read() { // ...
socket.read(input);
if (inputIsComplete()) {
state = PROCESSING;
pool.execute(new Processer());
}
}
synchronized void processAndHandOff() {
process();
state = SENDING; // or rebind attachment
sk.interest(SelectionKey.OP_WRITE);
}
class Processer implements Runnable {
public void run() { processAndHandOff(); }
}
}
Coordinating Tasks 协调任务
- Handoffs 传递性
每个任务启用、触发器或调用一个通常脆弱的但最快的 - Callbacks 回调每一个处理器 分发
设置状态,关联
变化 GoF Mediator pattern 模式 - Queues 队列
如 通过栈传输buffers - Futures 未来回执 预期
当一个任务处理有异步结果 协调层次关系 fork/join 或者 用wait/notify 同步 future.get()
Using PooledExecutor 使用自定义线程池
- 可调整的工作线程池
- 主方法 execute(Runnable r)
- 控制参数
任务队列的种类 (any Channel)
最大/最小线程数
“Warm” versus on-demand threads 热对比 需求的线程?
周期性 线程空闲 清理时间
必要才修改
Saturation policy 饱和策略
阻塞 block 丢弃 drop 立即本线程执行 producer-runs
Multiple Reactor Threads 多线程 Reactor
- 使用 REactor Pools 池
- 匹配 cpu 和 io 性能
- 静态或动态构建
每个有自己的 Selector Thread Dispatch loop - Main acceptor 分发到其他 reactors
Selector[] selectors; // also create threads
int next = 0;
class Acceptor { // ...
public synchronized void run() { ...
Socket connection = serverSocket.accept();
if (connection != null)
new Handler(selectors[next], connection);
if (++next == selectors.length) next = 0;
}
}
Using Multiple Reactors 使用多线程 Reactors
如图
Using other java.nio features 使用nio特性
- 每个Reactor有多个Selectors
需要小心使用sync锁去协调绑定不同的handlers处理器到不同的IO事件 - 文件传输
自动装配 file-to-net net-to-file 文件网络设备文件之间的复制 - Memory-mapped 内存映射文件
通过buffers跟文件交互 - Direct buffers 堆外直接内存
能够实现 zero-copy 零拷贝传输
但是有初始化分配和释放的开销
最时候需要 long-lived长连接的应用
Connection-Based Extensions 基于连接扩展
- Instead of a single service request 相对于单独的服务请求
Client建立连接
Client发送一系列的 消息/请求
Client断开连接 - 案例
数据和事物监控
多模块的游戏、聊天等 - 能扩展基础网络服务模式
处理很多实时长连接Clients
跟踪维持Client和Session 会话状态(包含下线,j2ee http无状态 无法把握下线时间)
通过多hosts 主机 分发服务
API Walkthrough api预览 详见jdk
Buffer ByteBuffer CharBuffer LongBuffer…
Channel
SelectableChannel
SocketChannel
ServerSocketChannel
FileChannel
Selector
SelectionKey