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.工作者线程池