从 Scalable IO in Java 走入nio的世界

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值