什麼是Reactor執行緒模型
Java中執行緒模型大致可以分為:
- 單執行緒模型
- 多執行緒模型
- 執行緒池模型(executor)
- Reactor執行緒模型
單執行緒模型中,server端使用一個執行緒來處理所有的請求,所有的請求必須序列化處理,效率低下。 多執行緒模型中,server端會為每個請求分配一個執行緒去處理請求,相對單執行緒模型而言多執行緒模型效率更高,但是多執行緒模型的缺點也很明顯:server端為每個請求都開闢一個執行緒來處理請求,如果請求數量很大,則會造成大量執行緒被建立,造成記憶體溢位。Java中執行緒是比較昂貴的物件。執行緒的數量不應該無限量的增大,當執行緒數超過一定數目後,增加執行緒不僅不能提高效率,反而會降低效率。 藉助"物件複用"的思想,執行緒池應運而生,執行緒池中一般有一定數目的執行緒,當請求數目超過執行緒數之後需要排隊等待。這樣就避免了執行緒會不停地增長。這裡不打算對Java的執行緒池做過多介紹,有興趣的可以去看我之前的文章:java執行緒池。
Reactor是一種處理模式。Reactor模式是處理併發I/O比較常見的一種模式,用於同步I/O,中心思想是將所有要處理的IO事件註冊到一箇中心I/O多路複用器上,同時主執行緒/程式阻塞在多路複用器上;一旦有I/O事件到來或是準備就緒(檔案描述符或socket可讀、寫),多路複用器返回並將事先註冊的相應I/O事件分發到對應的處理器中。
Reactor也是一種實現機制。Reactor利用事件驅動機制實現,和普通函式呼叫的不同之處在於:應用程式不是主動的呼叫某個API完成處理,而是恰恰相反,Reactor逆置了事件處理流程,應用程式需要提供相應的介面並註冊到Reactor上,如果相應的事件發生,Reactor將主動呼叫應用程式註冊的介面,這些介面又稱為“回撥函式”。用“好萊塢原則”來形容Reactor再合適不過了:不要打電話給我們,我們會打電話通知你。
為什麼需要Reactor模型
Reactor模型實質上是對I/O多路複用的一層包裝,理論上來說I/O多路複用的效率已經夠高了,為什麼還需要Reactor模型呢?答案是I/O多路複用雖然效能已經夠高了,但是編碼複雜,在工程效率上還是太低。因此出現了Reactor模型。
一個個網路請求可能涉及到多個I/O請求,相比傳統的單執行緒完整處理請求生命期的方法,I/O複用在人的大腦思維中並不自然,因為,程式設計師程式設計中,處理請求A的時候,假定A請求必須經過多個I/O操作A1-An(兩次IO間可能間隔很長時間),每經過一次I/O操作,再呼叫I/O複用時,I/O複用的呼叫返回裡,非常可能不再有A,而是返回了請求B。即請求A會經常被請求B打斷,處理請求B時,又被C打斷。這種思維下,程式設計容易出錯。
Reactor模型
一般來說處理一個網路請求需要經過以下五個步驟:
- 讀取請求資料(read request)
- 解碼資料(decode request)
- 計算,生成響應(compute)
- 編碼響應(encode response)
- 傳送響應資料(send response) 如下圖所示:
Reactor模型有三種執行緒模型:
- 單執行緒模型
- 多執行緒模型(單Reactor)
- 多執行緒模型(多Reactor)
單執行緒Reactor模型
單執行緒模型中Reactor既負責Accept新的連線請求,又負責分派請求到具體的handler中進行處理,一般不使用這種模型,因為單執行緒效率比較低下。
下面是基於Java NIO單執行緒Reactor模型的實現:
class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverSocket;
Reactor(int port) throws IOException { // Reactor設定
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(
new InetSocketAddress(port));
serverSocket.configureBlocking(false);
SelectionKey sk =
serverSocket.register(selector,
SelectionKey.OP_ACCEPT); // 監聽Socket連線事件
sk.attach(new Acceptor());
}
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();
}
class Acceptor implements Runnable { // inner
public void run() {
try {
SocketChannel c = serverSocket.accept();
if (c != null)
new Handler(selector, c);
} catch (IOException ex) { /* ... */ }
}
}
}
final class Handler implements Runnable {
static final int READING = 0, SENDING = 1;
final SocketChannel socket;
final SelectionKey sk;
ByteBuffer input = ByteBuffer.allocate(1024);
ByteBuffer output = ByteBuffer.allocate(1024);
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() { /* ... */ }
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();
}
}
複製程式碼
多執行緒模型(單Reactor)
該模型在事件處理器(Handler)鏈部分採用了多執行緒(執行緒池),也是後端程式常用的模型。模型圖如下:
多執行緒模型(多Reactor)
比起多執行緒單Rector模型,它是將Reactor分成兩部分,mainReactor負責監聽並Accept新連線,然後將建立的socket通過多路複用器(Acceptor)分派給subReactor。subReactor負責多路分離已連線的socket,讀寫網路資料;業務處理功能,其交給worker執行緒池完成。通常,subReactor個數上可與CPU個數等同。其模型圖如下:
Netty執行緒模型
Netty的執行緒模型類似於Reactor模型。Netty中ServerBootstrap用於建立服務端,下圖是它的結構:
下面是一個完整的Netty服務端的例子:
public class TimeServer {
public void bind(int port) {
// Netty的多Reactor執行緒模型,bossGroup是Acceptor執行緒池,用於接受連線。workGroup是Worker執行緒池,處理業務。
// bossGroup是Acceptor執行緒池
EventLoopGroup bossGroup = new NioEventLoopGroup();
// workGroup是Worker執行緒池
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
// 繫結埠,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服務端監聽埠關閉
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeServerHandler());
}
}
}
public class TimeServerHandler extends ChannelHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
// msg轉Buf
ByteBuf buf = (ByteBuf) msg;
// 建立緩衝中位元組數的位元組陣列
byte[] req = new byte[buf.readableBytes()];
// 寫入陣列
buf.readBytes(req);
String body = new String(req, "UTF-8");
String currenTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(
System.currentTimeMillis()).toString() : "BAD ORDER";
// 將要返回的資訊寫入Buffer
ByteBuf resp = Unpooled.copiedBuffer(currenTime.getBytes());
// buffer寫入通道
ctx.write(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// write讀入緩衝陣列後通過invoke flush寫入通道
ctx.flush();
}
}
複製程式碼