Doug Lea编写的一篇关于Java NIO以及Reactor模式的经典文章
理解该文章可以帮助我们更好的理解Netty网络编程的底层设计&运行思路
原文已经上传在线文档:【腾讯文档】Scalable_IO_in_Java
为了更好的理解本文章,请参阅我的列一篇文章:Java NIO:在网络编程中的基本范式
同时也可以关注我的相关专栏,谢谢大伙啦!!!
Doug Lea在本文中归纳了 web service、分布式对象(指代分布式设计)等都存在相似的基本构成:
由于上述的每一步的本质不同,相对应的成本也是不同的。
图示很明显,传统的服务是一种非常“垂直”的设计:
每当一个客户端与服务端建立连接后,服务端就开启一条新的线程来供相关处理单元处理该客户端的业务请求
package com.leolee.netty.scalableIOInJava.classicServiceDesigns;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @ClassName Server
* @Description: 传统IO服务设计
* @Author LeoLee
* @Date 2020/10/16
* @Version V1.0
**/
public class Server implements Runnable {
private static final int PORT = 8899;
private static final int MAX_INPUT = 1024;
@Override
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 e) {
e.printStackTrace();
}
}
static class Handler implements Runnable {
final Socket socket;
Handler(Socket s) {
socket = s;
}
@Override
public void run() {
try {
byte[] input = new byte[MAX_INPUT];
socket.getInputStream().read(input);
byte[] output = process(input);
socket.getOutputStream().write(output);
} catch (IOException e) {
e.printStackTrace();
}
}
//实际业务处理并返回处理结果
private byte[] process(byte[] cmd) {
/* ... */
return null;
}
}
}
Doug Lea用这个示例简单明了的实现了传统服务设计:
缺点:
这样的设计最严重的缺点就是会为每一个请求创建线程,高并发情况下,过去的线程被创建,严重影响服务器的性能,服务器所承载的最大线程数也是有上限的,CPU在线程间切换的开销也是极大的。同时 ServerSocket.accept() 方法是阻塞的,该方法将监听连接在此socket上的建立请求并建立socket连接,将阻塞到建立为止。
Doug Lea提出了实现可伸缩性的基本要求:
reactor模式主要的应用场景就是在网络编程上
Reactor responds to IO events by dispatching the appropriate handler(Reactor通过分发恰当的处理器来响应IO事件)
Similar to AWT thread
Handlers perform non-blocking actions(处理器执行非阻塞操作)
Similar to AWT ActionListeners
Manage by binding handlers to events(通过将处理器绑定到事件来管理)
Similar to AWT addActionListener
See Schmidt et al, Pattern-OrientedSoftwareArchitecture,Volume2(POSA2)
Also Richard Stevens's networking books, Matt Welsh's SEDA framework, etc
实际上 Reactor 就是个 EventLoop(事件循环组),Netty 中的 EventLoopGroup 就是 Reactor的具体实现
NIO就不介绍了,看这篇文章的大伙应该都懂的
Reactor:
package com.leolee.netty.scalableIOInJava.classicServiceDesigns;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* @ClassName Reactor
* @Description: 响应器(也就是事件循环组)
* @Author LeoLee
* @Date 2020/10/16
* @Version V1.0
**/
public 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);
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();
*/
//----------------------------------------------------------------
//--------------------------Dispatch Loop()---------------------
//----------------------------------------------------------------
@Override
public void run() {// 通常这是在一个新的线程中执行的
while (!Thread.interrupted()) {
try {
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext()) {
//开始根据每一个selectedKeys派发
dispatch((SelectionKey) (it.next()));
}
selected.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
}
void dispatch(SelectionKey k) {
Runnable r = (Runnable) (k.attachment());
if (r != null) {
r.run();
}
}
//----------------------------------------------------------------
//--------------------------Acceptor------------------------------
//----------------------------------------------------------------
/**
* @ClassName Acceptor
* @Description: 请求接收器
* @Author LeoLee
* @Date 2020/10/16
* @Version V1.0
**/
public class Acceptor implements Runnable {
@Override
public void run() {
try {
SocketChannel c = serverSocket.accept();
if (c != null) {
new Handler(selector, c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Handler:
package com.leolee.netty.scalableIOInJava.classicServiceDesigns;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
/**
* @ClassName Handler
* @Description: 处理器
* @Author LeoLee
* @Date 2020/10/16
* @Version V1.0
**/
public class Handler implements Runnable {
//----------------------------------------------------------------
//--------------------------Handler setup-------------------------
//----------------------------------------------------------------
final SocketChannel socket;
final SelectionKey sk;
static int MAXIN, MAXOUT = 1024;
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() {
/* ... */
return false;
}
boolean outputIsComplete() {
/* ... */
return false;
}
void process() {
/* ... */
}
//----------------------------------------------------------------
//--------------------------Request handling----------------------
//----------------------------------------------------------------
@Override
public void run() {
try {
if (state == READING) {
read();
} else if (state == SENDING) {
send();
}
} catch (IOException e) {
e.printStackTrace();
}
}
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:A simple use of GoF State-Object pattern(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();
}
}
}
Normally need many fewer threads than clients(使用线程池可以用更少的线程来应对更多的客户端)
改造之前单线程版本的Handler如下:
package com.leolee.netty.scalableIOInJava;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @ClassName HandlerWithThreadPool
* @Description: 使用线程池来编写受控且可调节的处理器
* @Author LeoLee
* @Date 2020/10/17
* @Version V1.0
**/
public class HandlerWithThreadPool implements Runnable {
// uses util.concurrent thread pool
// static PooledExecutor pool = new PooledExecutor(...);
static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
static final int PROCESSING = 3;
// ...
final SocketChannel socket;
final SelectionKey sk;
static int MAXIN, MAXOUT = 1024;
ByteBuffer input = ByteBuffer.allocate(MAXIN);
ByteBuffer output = ByteBuffer.allocate(MAXOUT);
static final int READING = 0, SENDING = 1;
int state = READING;
HandlerWithThreadPool(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() {
/* ... */
return false;
}
boolean outputIsComplete() {
/* ... */
return false;
}
void process() {
/* ... */
}
synchronized void read() {
// ...
try {
socket.read(input);
if (inputIsComplete()) {
state = PROCESSING;
cachedThreadPool.execute(new Processer());
}
} catch (IOException e) {
e.printStackTrace();
}
}
synchronized void send2() {
cachedThreadPool.execute(new WriteProcesser());
}
void send() throws IOException {
socket.write(output);
if (outputIsComplete()) {
sk.cancel();
}
}
synchronized void processAndHandOff() {
process();
state = SENDING; // or rebind attachment
sk.interestOps(SelectionKey.OP_WRITE);
}
class Processer implements Runnable {
public void run() {
processAndHandOff();
}
}
class WriteProcesser implements Runnable {
@Override
public void run() {
try {
send();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
if (state == READING) {
read();
} else if (state == SENDING) {
send2();
}
}
}
创建MainReactor类(MainReactor类似于Netty中的bossGroup或者是parentGroup,subReactor类似于workerGroup或者是childGroup),Acceptor部分如下
需要注意的是这里使用多个 Selector来管理不同的Channel,接收器将特定的Selector传递给Handler处理
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;
}
}
这一部分就不仔细分析了,根据实际情况的不同,使用不同的处理方式