Scalable IO in Java 译文

本文深入探讨了Java中实现可伸缩网络服务的方法,包括事件驱动设计、Reactor模式及其多线程实现。文章详细阐述了如何利用java.nio非阻塞API,通过分而治之的策略提高服务的性能和资源利用率。核心概念如经典ServerSocket循环、Acceptor、Handler的设置和请求处理等都有详尽的解释。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.概要

  • 可伸缩的网络服务
  • 事件驱动程序
  • Reactor模式(基本版本,多线程版本,其他变型)
  • 参考了解java.nio中有关非阻塞的API

 

2.网络服务

  • web服务,分布式对象等等
  • 大都具有相同的基本结构:读请求 ——》 解码请求 ——》 处理请求 ——》 编码响应 ——》 发送响应    
  • 但是,每一步都有不同的性质和成本

 

3.经典服务设计

每一个hanlder可以由它的主线程启动。

3.1 经典ServerSocket循环

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;
    //这里接受ServerSocket的accept方法返回的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: 在这个示例中大部分的异常都已省略

4.可伸缩性目标

  • 载荷作用下的退化(多客户端)
  • 随着资源(CPU、内存、磁盘、带宽)的增加而不断改进
  • 满足可用性和性能目标
  • 分治法通常是实现任何可伸缩性目标的最佳方法

5.分而治之

  • 将整个处理过程分为多个小的任务(每个任务都是一个非阻塞的行为)
  • 当任务可以执行时对任务进行执行

这里,IO事件经常充当触发器:

  • java.nio支持
  • 多种变化可能

6.事件驱动设计

  • 更加效率

更少的资源 —— 不需要为每个客户端创建一个线程

更少的负载 —— 减少了线程上下文切换和锁的开销

发送可能变得更慢 —— 必须手动地为事件绑定相应的动作

  • 编程变得困难

必须划分程简单的非阻塞行为(类似GUI事件驱动行为)(不能消除所有的阻塞:GC,页面错误等等)

必须跟踪服务的逻辑状态

 

7.AWT中的事件

IO事件驱动和这个有相似的部分但是是通过不同的设计方法。

8.Reactor模式

  • Reactor通过调度适当的handler来响应IO事件
  • Handlers执行非阻塞操作
  • 将hanlder绑定到事件上来进行管理

9.基本的Reactor设计

单线程版本

10.java.nio支持

  • Channels:连接文件和socket,支持非阻塞读
  • BUffers:类似于数组,可以被Channel直接读取和写入
  • Selectors:返回有IO事件产生的Channel集合
  • SelectionKeys:维护IO事件状态和绑定的Channel

11

 11.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.configureBlocking(false);
		//阻塞感兴趣事件为ACCEPT
		SelectionKey sk = serverSocket.register(selector,SelectionKey.OP_ACCEPT); 
		//添加Accept对象
		sk.attach(new Acceptor());
	}
/*
也可以使用如下代码替换:
SelectorProvider p = SelectorProvider.provider(); 
selector = p.openSelector();
serverSocket = p.openServerSocketChannel();
 */

 

11.2 Dispatch Loop

// class Reactor continued
  public void run() {  // 一般在一个新的线程中
	try {
		while (!Thread.interrupted()) {
			//阻塞方法
			selector.select();
			//获取SelectedKey集合
			Set selected = selector.selectedKeys(); 
			Iterator it = selected.iterator(); 
			while (it.hasNext())
				dispatch((SelectionKey)(it.next()); 
			//最后一定要清空集合
			selected.clear();
		}
	} catch (IOException ex) { /* ... */ }
  }
  void dispatch(SelectionKey k) {
  	//获取相应的Acceptor
    Runnable r = (Runnable)(k.attachment()); 
    if (r != null)
		r.run(); 
  }

11.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) { /* ... */ } 
	}
 } }

11.4 Handler setup

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); 
		//设置感兴趣事件为READ
		sk.interestOps(SelectionKey.OP_READ); 
		//select方法会立刻返回结果
		sel.wakeup();
	}
	boolean inputIsComplete() { /* ... */ } 
	boolean outputIsComplete() { /* ... */ } 
	void process() { /* ... */ }

11.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();
	} 
}

11.6 Per-State Handlers

GoF状态对象模式的简单使用。

将适当的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(); 
		}
	}
}

12.多线程设计

  • 通过适当的添加线程来增加可伸缩性(主要适用于多处理器)
  • 工作线程

Reactor能够快速触发handler;

将非IO流程放到其他线程进行处理。

  • 多个Reactor线程

 

13.工作者线程

  • 去掉非IO流程以加快Reactor线程
  • 比将计算绑定处理重新处理为事件驱动的形式更简单
  • 但是比较困难地处理重复的IO
  • 使用线程池来进行协调和控制

 

14.工作者线程池

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值