Java中Reactor的反應器模式詳解
前言
在Java的OIO編程中,最初和最原始的網(wǎng)絡服務器程序使用一個while循環(huán),不斷地監(jiān)聽端口是否有新的連接,如果有就調(diào)用一個處理函數(shù)來處理。這種方法最大的問題就是如果前一個網(wǎng)絡連接的處理沒有結(jié)束,那么后面的連接請求沒法被接收,于是后面的請求統(tǒng)統(tǒng)會被阻塞住,服務器的吞吐量就太低了。
為了解決這個嚴重的連接阻塞問題,出現(xiàn)了一個即為經(jīng)典模式:Connection Per Thread。即對于每一個新的網(wǎng)絡連接都分配一個線程,每個線程都獨自處理自己負責的輸入和輸出,任何socket連接的輸入和輸出處理不會阻塞到后面新socket連接的監(jiān)聽和建立。早期版本的Tomcat服務器就是這樣實現(xiàn)的。
這種模式的優(yōu)點是解決了前面的新連接被嚴重阻塞的問題,在一定程度上極大地提高了服務器的吞吐量。但是對于大量的連接,需要消耗大量的現(xiàn)成資源,如果線程數(shù)太多,系統(tǒng)無法承受。而且線程的反復創(chuàng)建、銷毀、線程的切換也需要代價。因此高并發(fā)應用場景下多線程OIO的缺陷是致命的,因此引入了Reactor反應器模式。
反應器模式由Reactor反應器線程、Handlers處理器兩大角色組成:
- Reactor反應器線程的職責:負責響應IO事件,并且分發(fā)到Handlers處理器
- Handlers處理器的職責:非阻塞的執(zhí)行業(yè)務處理邏輯
一、單線程Reactor反應器模式
Reactor反應器模式有點兒類似事件驅(qū)動模式,當有事件觸發(fā)時,事件源會將事件dispatch分發(fā)到handler處理器進行事件處理。反應器模式中的反應器角色類似于事件驅(qū)動模式中的dispatcher事件分發(fā)器角色。
- Reactor反應器:負責查詢IO事件,當檢測到一個IO時間,將其發(fā)送給對應的Handler處理器處理,這里的IO事件就是NIO選擇器監(jiān)控的通道IO事件。
- Handler處理器:與IO事件綁定,負責IO事件的處理,完成真正的連接建立、通道的讀取、處理業(yè)務邏輯、負責將結(jié)果寫出到通道等。
基于NIO實現(xiàn)單線程版本的反應器模式需要用到SelectionKey選擇鍵的幾個重要的成員方法:
- void attach(Object o):將任何的Java對象作為附件添加到SelectionKey實例,主要是將Handler處理器實例作為附件添加到SelectionKey實例
- Object attachment():取出之前通過attach添加到SelectionKey選擇鍵實例的附件,一般用于取出綁定的Handler處理器實例。
Reactor實現(xiàn)示例:
package cn.ken.jredis;
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.Set;
/**
* <pre>
*
* </pre>
*
* @author <a rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Ken-Chy129</a>
* @since 2023/10/14 14:29
*/
public class Reactor implements Runnable {
final private Selector selector;
final private ServerSocketChannel serverSocketChannel;
public Reactor() {
try {
this.selector = Selector.open();
this.serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8088));
// 注冊ServerSocket的accept事件
SelectionKey sk = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 為事件綁定處理器
sk.attach(new AcceptHandler());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectedKey : selectionKeys) {
dispatch(selectedKey);
}
selectionKeys.clear();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void dispatch(SelectionKey selectedKey) {
Runnable handler = (Runnable) selectedKey.attachment();
// 此處返回的可能是AcceptHandler也可能是IOHandler
handler.run();
}
class AcceptHandler implements Runnable {
@Override
public void run() {
try {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
new IOHandler(selector, socketChannel); // 注冊IO處理器,并將連接加入select列表
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
new Reactor().run();
}
}Handler實現(xiàn)示例:
package cn.ken.jredis;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
/**
* <pre>
*
* </pre>
*
* @author <a rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Ken-Chy129</a>
* @since 2023/10/14 14:53
*/
public class IOHandler implements Runnable {
final private SocketChannel socketChannel;
final private ByteBuffer buffer;
public IOHandler(Selector selector, SocketChannel channel) {
buffer = ByteBuffer.allocate(1024);
socketChannel = channel;
try {
channel.configureBlocking(false);
SelectionKey sk = channel.register(selector, 0); // 此處沒有注冊感興趣的事件
sk.attach(this);
sk.interestOps(SelectionKey.OP_READ); // 注冊感興趣的事件,下一次調(diào)用select時才生效
selector.wakeup(); // 立即喚醒當前阻塞select操作,使得迅速進入下次select,從而讓上面注冊的讀事件監(jiān)聽可以立即生效
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
try {
int length;
while ((length = socketChannel.read(buffer)) > 0) {
System.out.println(new String(buffer.array(), 0, length));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}在單線程反應器模式中,Reactor反應器和Handler處理器都執(zhí)行在同一條線程上(dispatch方法是直接調(diào)用run方法,沒有創(chuàng)建新的線程),因此當其中某個Handler阻塞時,會導致其他所有的Handler都得不到執(zhí)行。
二、多線程Reactor反應器模式
既然Reactor反應器和Handler處理器在一個線程會造成非常嚴重的性能缺陷,那么可以使用多線程對基礎(chǔ)的反應器模式進行改造。
- 將負責輸入輸出處理的IOHandler處理器的執(zhí)行,放入獨立的線程池中。這樣業(yè)務處理線程與負責服務監(jiān)聽和IO時間查詢的反應器線程相隔離,避免服務器的連接監(jiān)聽收到阻塞。
- 如果服務器為多核的CPU,可以將反應器線程拆分為多個子反應器線程,同時引入多個選擇器,每一個SubReactor子線程負責一個選擇器。
MultiReactor:
package cn.ken.jredis;
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.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <pre>
*
* </pre>
*
* @author <a rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Ken-Chy129</a>
* @since 2023/10/14 16:51
*/
public class MultiReactor {
private final ServerSocketChannel server;
private final Selector[] selectors = new Selector[2];
private final SubReactor[] reactors = new SubReactor[2];
private final AtomicInteger index = new AtomicInteger(0);
public MultiReactor() {
try {
server = ServerSocketChannel.open();
selectors[0] = Selector.open();
selectors[1] = Selector.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
SelectionKey register = server.register(selectors[0], SelectionKey.OP_ACCEPT);
register.attach(new AcceptHandler());
reactors[0] = new SubReactor(selectors[0]);
reactors[1] = new SubReactor(selectors[1]);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void startService() {
new Thread(reactors[0]).start();
new Thread(reactors[1]).start();
}
class SubReactor implements Runnable {
final private Selector selector;
public SubReactor(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
while (!Thread.interrupted()) {
try {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
dispatch(selectionKey);
}
selectionKeys.clear();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
private void dispatch(SelectionKey selectionKey) {
Runnable attachment = (Runnable) selectionKey.attachment();
if (attachment != null) {
attachment.run();
}
}
class AcceptHandler implements Runnable {
@Override
public void run() {
try {
SocketChannel socketChannel = server.accept();
new MultiHandler(selectors[index.getAndIncrement()], socketChannel);
if (index.get() == selectors.length) {
index.set(0);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}MultiHandler:
package cn.ken.jredis;
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;
/**
* <pre>
*
* </pre>
*
* @author <a rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Ken-Chy129</a>
* @since 2023/10/14 17:28
*/
public class MultiHandler implements Runnable {
final private Selector selector;
final private SocketChannel channel;
final ByteBuffer buffer = ByteBuffer.allocate(1024);
static ExecutorService pool = Executors.newFixedThreadPool(4);
public MultiHandler(Selector selector, SocketChannel channel) {
this.selector = selector;
this.channel = channel;
try {
channel.configureBlocking(false);
SelectionKey register = channel.register(selector, SelectionKey.OP_READ);
register.attach(this);
selector.wakeup();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
pool.execute(() -> {
synchronized (this) {
int length;
try {
while ((length = channel.read(buffer)) > 0) {
System.out.println(new String(buffer.array(), 0, length));
buffer.clear();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}到此這篇關(guān)于Java中Reactor的反應器模式詳解的文章就介紹到這了,更多相關(guān)Reactor反應器模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
httpclient getPoolEntryBlocking連接池方法源碼解讀
這篇文章主要為大家介紹了httpclient getPoolEntryBlocking連接池方法源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-11-11
SpringCloud Gateway鑒權(quán)和跨域解決方案
網(wǎng)關(guān)是介于客戶端和服務器端之間的中間層,所有的外部請求都會先經(jīng)過 網(wǎng)關(guān)這一層,也就是說,API 的實現(xiàn)方面更多的考慮業(yè)務邏輯,而安全、性能、監(jiān)控可以交由 網(wǎng)關(guān)來做,這樣既提高業(yè)務靈活性又不缺安全性,本文給大家介紹SpringCloud Gateway鑒權(quán)和跨域解決方案,一起看看吧2023-11-11
深入Java Robot實現(xiàn)控制鼠標和鍵盤的方法詳解
本篇文章是對Java中用Robot實現(xiàn)控制鼠標和鍵盤的方法進行了詳細的分析介紹,需要的朋友參考下2013-05-05

