前言——服務(wù)端處理網(wǎng)絡(luò)請求
首先看看服務(wù)端處理網(wǎng)絡(luò)請求的典型過程:
由上圖可以看到,主要處理步驟包括:
- 獲取請求數(shù)據(jù)婴洼,客戶端與服務(wù)器建立連接發(fā)出請求,服務(wù)器接受請求(1-3)撼嗓。
- 構(gòu)建響應(yīng)柬采,當(dāng)服務(wù)器接收完請求欢唾,并在用戶空間處理客戶端的請求,直到構(gòu)建響應(yīng)完成(4)粉捻。
- 返回數(shù)據(jù)礁遣,服務(wù)器將已構(gòu)建好的響應(yīng)再通過內(nèi)核空間的網(wǎng)絡(luò) I/O 發(fā)還給客戶端(5-7)。
設(shè)計服務(wù)端并發(fā)模型時肩刃,主要有如下兩個關(guān)鍵點:
- 1祟霍、服務(wù)器如何管理連接,獲取輸入數(shù)據(jù)盈包。
- 2沸呐、服務(wù)器如何處理請求。
以上兩個關(guān)鍵點最終都與操作系統(tǒng)的 I/O 模型以及線程(進程)模型相關(guān)呢燥,下面詳細介紹這兩個模型垂谢。
前言——I/O多路復(fù)用
I/O多路復(fù)用是指使用一個線程來檢查多個文件描述符(Socket)的就緒狀態(tài),比如調(diào)用select和poll函數(shù),傳入多個文件描述符铜幽,如果有一個文件描述符就緒园匹,則返回,否則阻塞直到超時肪虎。得到就緒狀態(tài)后進行真正的操作可以在同一個線程里執(zhí)行,也可以啟動線程執(zhí)行(比如使用線程池)。
一般情況下畸裳,I/O 復(fù)用機制需要事件分發(fā)器。 事件分發(fā)器的作用淳地,將那些讀寫事件源分發(fā)給各讀寫事件的處理者怖糊。
涉及到事件分發(fā)器的兩種模式稱為:Reactor和Proactor。 Reactor模式是基于同步I/O的颇象,而Proactor模式是和異步I/O相關(guān)的伍伤。本文主要介紹的就是 Reactor模式相關(guān)的知識。
一遣钳、傳統(tǒng)阻塞IO服務(wù)模型——BIO模式
模型特點
- 1扰魂、采用阻塞IO模式獲取輸入的數(shù)據(jù)。
- 2蕴茴、每個連接都需要獨立的線程完成數(shù)據(jù)的輸入劝评,業(yè)務(wù)處理,數(shù)據(jù)返回。
針對傳統(tǒng)阻塞 I/O 服務(wù)模型的 2 個缺點倦淀,解決方案:
1蒋畜、基于 I/O 復(fù)用模型:多個連接共用一個阻塞對象,應(yīng)用程序只需要在一個阻塞對象等待撞叽,無需阻塞等待所有連接姻成。當(dāng)某個連接有新的數(shù)據(jù)可以處理時插龄,操作系統(tǒng)通知應(yīng)用程序,線程從阻塞狀態(tài)返回佣渴,開始進行業(yè)務(wù)處理辫狼。
2、基于線程池復(fù)用線程資源:不必再為每個連接創(chuàng)建線程辛润,將連接完成后的業(yè)務(wù)處理任務(wù)分配給線程進行處理膨处,一個線程可以處理多個連接的業(yè)務(wù)。
Reactor 對應(yīng)的叫法:1砂竖、反應(yīng)器模式真椿;2、分發(fā)者模式(Dispatcher)乎澄;3突硝、通知者模式(notifier)
二、Reactor模式簡介
Netty是典型的Reactor模型結(jié)構(gòu)置济,關(guān)于Reactor的詳盡闡釋解恰,本文站在巨人的肩膀上,借助 Doug Lea(就是那位讓人無限景仰的大爺)的“Scalable IO in Java”中講述的Reactor模式浙于。
“Scalable IO in Java”的地址是:http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
Reactor:是反應(yīng)堆的意思护盈,Reactor 模型是指通過一個或多個輸入同時傳遞給服務(wù)處理器的服務(wù)請求的事件驅(qū)動處理模式。
服務(wù)端程序處理傳入多路請求羞酗,并將它們同步分派給請求對應(yīng)的處理線程腐宋,Reactor 模式也叫 Dispatcher 模式,即 I/O 多路復(fù)用統(tǒng)一監(jiān)聽事件檀轨,收到事件后分發(fā)(Dispatch 給某進程)胸竞,是編寫高性能網(wǎng)絡(luò)服務(wù)器的必備技術(shù)之一,大多數(shù)IO相關(guān)組件如Netty参萄、Redis在使用的IO模式卫枝。
Reactor 模型中有 2 個關(guān)鍵組成:
Reactor:Reactor 在一個單獨的線程中運行,負(fù)責(zé)監(jiān)聽和分發(fā)事件讹挎,分發(fā)給適當(dāng)?shù)奶幚沓绦騺韺?IO 事件做出反應(yīng)剃盾。它就像公司的電話接線員,它接聽來自客戶的電話并將線路轉(zhuǎn)移到適當(dāng)?shù)穆?lián)系人淤袜。
Handlers:處理程序執(zhí)行 I/O 事件要完成的實際事件痒谴,類似于客戶想要與之交談的公司中的實際官員。Reactor 通過調(diào)度適當(dāng)?shù)奶幚沓绦騺眄憫?yīng) I/O 事件铡羡,處理程序執(zhí)行非阻塞操作积蔚。
取決于 Reactor 的數(shù)量和 Hanndler 線程數(shù)量的不同,Reactor 模型有 3 個變種:
- 單 Reactor 單線程烦周。
- 單 Reactor 多線程尽爆。
- 主從 Reactor 多線程怎顾。
三、多線程IO的致命缺陷
最最原始的網(wǎng)絡(luò)編程思路就是服務(wù)器用一個while循環(huán)漱贱,不斷監(jiān)聽端口是否有新的套接字連接槐雾,如果有,那么就調(diào)用一個處理函數(shù)處理幅狮,類似:
while(true){
socket = accept();
handle(socket)
}
這種方法的最大問題是無法并發(fā)募强,效率太低,如果當(dāng)前的請求沒有處理完崇摄,那么后面的請求只能被阻塞擎值,服務(wù)器的吞吐量太低。
之后逐抑,想到了使用多線程鸠儿,也就是很經(jīng)典的connection per thread,每一個連接用一個線程處理厕氨,類似:
class BasicModel implements Runnable {
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(8888);
while (!Thread.interrupted()) {
//創(chuàng)建新線程來handle
// or, single-threaded, or a thread pool
new Thread(new Handler(serverSocket.accept())).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
static class Handler implements Runnable {
private Socket socket;
public Handler(Socket socket) {
this.socket = socket;
}
public void run() {
try {
byte[] input = new byte[1024];
socket.getInputStream().read(input);
byte[] output = process(input);
socket.getOutputStream().write(output);
} catch (IOException e) {
e.printStackTrace();
}
}
private byte[] process(byte[] input) {
byte[] output=null;
//業(yè)務(wù)邏輯處理
return output;
}
}
}
對于每一個請求都分發(fā)給一個線程进每,每個線程中都獨自處理上面的流程。
tomcat服務(wù)器的早期版本確實是這樣實現(xiàn)的命斧。
多線程并發(fā)模式品追,一個連接一個線程的優(yōu)點是:
- 一定程度上極大地提高了服務(wù)器的吞吐量,因為之前的請求在read阻塞以后冯丙,不會影響到后續(xù)的請求,因為他們在不同的線程中遭京。這也是為什么通常會講“一個線程只能對應(yīng)一個socket”的原因胃惜。另外有個問題,如果一個線程中對應(yīng)多個socket連接不行嗎哪雕?語法上確實可以船殉,但是實際上沒有用,每一個socket都是阻塞的斯嚎,所以在一個線程里只能處理一個socket利虫,就算accept了多個也沒用,前一個socket被阻塞了堡僻,后面的是無法被執(zhí)行到的糠惫。
多線程并發(fā)模式,一個連接一個線程的缺點是:
- 缺點在于資源要求太高钉疫,系統(tǒng)中創(chuàng)建線程是需要比較高的系統(tǒng)資源的硼讽,如果連接數(shù)太高,系統(tǒng)無法承受牲阁,而且固阁,線程的反復(fù)創(chuàng)建-銷毀也需要代價壤躲。
改進方法是:
- 采用基于事件驅(qū)動的設(shè)計,當(dāng)有事件觸發(fā)時备燃,才會調(diào)用處理器進行數(shù)據(jù)處理碉克。使用Reactor模式,對線程的數(shù)量進行控制并齐,一個線程處理大量的事件漏麦。
四、單 Reactor 單線程
方案說明:
- 1冀膝、Select 是前面 I/O 復(fù)用模型介紹的標(biāo)準(zhǔn)網(wǎng)絡(luò)編程 API唁奢,可以實現(xiàn)應(yīng)用程序通過一個阻塞對象監(jiān)聽多路連接請求。
- 2窝剖、Reactor 對象通過 Select 監(jiān)控客戶端請求事件麻掸,收到事件后通過 Dispatch 進行分發(fā)。
- 3赐纱、如果是建立連接請求事件脊奋,則由 Acceptor 通過 Accept 處理連接請求,然后創(chuàng)建一個 Handler 對象處理連接完成后的后續(xù)業(yè)務(wù)處理疙描。
- 4诚隙、如果不是建立連接事件,則 Reactor 會分發(fā)調(diào)用連接對應(yīng)的 Handler 來響應(yīng)起胰。
- 5久又、Handler 會完成 Read→業(yè)務(wù)處理→Send 的完整業(yè)務(wù)流程。
服務(wù)器端用一個線程通過多路復(fù)用搞定所有的 IO 操作(包括連接效五,讀地消、寫等),編碼簡單畏妖,清晰明了脉执,但是如果客戶端連接數(shù)量較多,將無法支撐戒劫,下面的NIO就屬于這種模型半夷。
Reactor模型的樸素原型
Java的NIO模式的Selector網(wǎng)絡(luò)通訊,其實就是一個簡單的Reactor模型迅细∥组希可以說是Reactor模型的樸素原型。
public class NIOServer {
public static void main() throws IOException {
// 1茵典、獲取Selector選擇器
Selector selector = Selector.open();
// 2嗦随、獲取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 3.設(shè)置為非阻塞
serverSocketChannel.configureBlocking(false);
// 4、綁定連接
serverSocketChannel.bind(new InetSocketAddress(8888));
// 5、將通道注冊到選擇器上,并注冊的操作為:“接收”操作
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 6枚尼、采用輪詢的方式贴浙,查詢獲取“準(zhǔn)備就緒”的注冊過的操作
while (true) {
if(selector.select() == 0){
continue;
}
// 7、獲取當(dāng)前選擇器中所有注冊的選擇鍵(“已經(jīng)準(zhǔn)備就緒的操作”)
Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
// 8署恍、獲取“準(zhǔn)備就緒”的時間
SelectionKey selectedKey = selectedKeys.next();
// 9崎溃、判斷key是具體的什么事件
if (selectedKey.isAcceptable()) {
// 10、若接受的事件是“接收就緒” 操作,就獲取客戶端連接
SocketChannel socketChannel = serverSocketChannel.accept();
// 11盯质、切換為非阻塞模式
socketChannel.configureBlocking(false);
// 12袁串、將該通道注冊到selector選擇器上
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectedKey.isReadable()) {
// 13、獲取該選擇器上的“讀就緒”狀態(tài)的通道
SocketChannel socketChannel = (SocketChannel) selectedKey.channel();
// 14呼巷、讀取數(shù)據(jù)
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int length = 0;
while ((length = socketChannel.read(byteBuffer)) != -1)
{
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, length));
byteBuffer.clear();
}
socketChannel.close();
}
// 15囱修、移除選擇鍵
selectedKeys.remove();
}
}
}
}
實際上的Reactor模式,是基于Java NIO的王悍,在他的基礎(chǔ)上破镰,抽象出來兩個組件——Reactor和Handler兩個組件:
1)Reactor:負(fù)責(zé)響應(yīng)IO事件,當(dāng)檢測到一個新的事件压储,將其發(fā)送給相應(yīng)的Handler去處理鲜漩;新的事件包含連接建立就緒、讀就緒集惋、寫就緒等孕似。
2)Handler:將自身(handler)與事件綁定,負(fù)責(zé)事件的處理刮刑,完成channel的讀入喉祭,完成處理業(yè)務(wù)邏輯后,負(fù)責(zé)將結(jié)果寫出channel雷绢。
4.1泛烙、什么是單線程Reactor呢?
如下圖所示:
這是最簡單的單Reactor單線程模型习寸。Reactor線程是個多面手,負(fù)責(zé)多路分離套接字傻工,Accept新連接霞溪,并分派請求到Handler處理器中。
下面的圖中捆,來自于“Scalable IO in Java”鸯匹,和上面的圖的意思,差不多泄伪。Reactor和Hander 處于一條線程執(zhí)行殴蓬。
順便說一下,可以將上圖的accepter,看做是一種特殊的handler染厅。
4.2痘绎、單線程Reactor的參考代碼
“Scalable IO in Java”,實現(xiàn)了一個單線程Reactor的參考代碼肖粮,Reactor的代碼如下:
public class Handler implements Runnable {
private SocketChannel channel;
private SelectionKey selectionKey;
ByteBuffer input = ByteBuffer.allocate(1024);
ByteBuffer output = ByteBuffer.allocate(1024);
static final int READING = 0, SENDING = 1;
int state = READING;
public Handler(Selector selector, SocketChannel c) throws IOException {
this.channel = c;
c.configureBlocking(false);
// Optionally try first read now
this.selectionKey = channel.register(selector, 0);
//將Handler作為callback對象
this.selectionKey.attach(this);
//第二步,注冊Read就緒事件
this.selectionKey.interestOps(SelectionKey.OP_READ);
selector.wakeup();
}
private boolean inputIsComplete() {
/* ... */
return false;
}
private boolean outputIsComplete() {
/* ... */
return false;
}
private void process() {
/* ... */
return;
}
public void run() {
try {
if (state == READING) {
read();
} else if (state == SENDING) {
send();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void read() throws IOException {
channel.read(input);
if (inputIsComplete()) {
process();
state = SENDING;
// Normally also do first write now
//第三步,接收write就緒事件
selectionKey.interestOps(SelectionKey.OP_WRITE);
}
}
private void send() throws IOException {
channel.write(output);
//write完就結(jié)束了, 關(guān)閉select key
if (outputIsComplete()) {
selectionKey.cancel();
}
}
}
這兩段代碼孤页,是建立在JAVA NIO的基礎(chǔ)上的,這兩段代碼建議一定要看懂涩馆⌒惺可以在IDE中去看源碼,這樣直觀感覺更佳魂那。
4.3蛾号、單線程模式的缺點:
1、 當(dāng)其中某個 handler 阻塞時涯雅, 會導(dǎo)致其他所有的 client 的 handler 都得不到執(zhí)行鲜结, 并且更嚴(yán)重的是, handler 的阻塞也會導(dǎo)致整個服務(wù)不能接收新的 client 請求(因為 acceptor 也被阻塞了)斩芭。 因為有這么多的缺陷轻腺, 因此單線程Reactor 模型用的比較少。這種單線程模型不能充分利用多核資源划乖,所以實際使用的不多贬养。
2、因此琴庵,單線程模型僅僅適用于handler 中業(yè)務(wù)處理組件能快速完成的場景误算。
方案優(yōu)缺點分析:
優(yōu)點:模型簡單,沒有多線程迷殿、進程通信儿礼、競爭的問題,全部都在一個線程中完成庆寺。
缺點:性能問題蚊夫,只有一個線程,無法完全發(fā)揮多核 CPU 的性能懦尝。Handler 在處理某個連接上的業(yè)務(wù)時知纷,整個進程無法處理其他連接事件,很容易導(dǎo)致性能瓶頸陵霉。
缺點:可靠性問題琅轧,線程意外終止,或者進入死循環(huán)踊挠,會導(dǎo)致整個系統(tǒng)通信模塊不可用乍桂,不能接收和處理外部消息,造成節(jié)點故障。
使用場景:客戶端的數(shù)量有限睹酌,業(yè)務(wù)處理非橙ㄋ快速,比如 Redis在業(yè)務(wù)處理的時間復(fù)雜度 O(1) 的情況忍疾。
五闯传、多線程的Reactor
方案說明:
- 1、Reactor 對象通過select 監(jiān)控客戶端請求事件卤妒,收到事件后甥绿,通過dispatch進行分發(fā)。
- 2则披、如果建立連接請求共缕,則右Acceptor 通過accept 處理連接請求,然后創(chuàng)建一個Handler對象處理完成連接后的各種事件士复。
- 3图谷、如果不是連接請求,則由reactor分發(fā)調(diào)用連接對應(yīng)的handler 來處理阱洪。
- 4便贵、handler 只負(fù)責(zé)響應(yīng)事件,不做具體的業(yè)務(wù)處理, 通過read 讀取數(shù)據(jù)后冗荸,會分發(fā)給后面的worker線程池的某個線程處理業(yè)務(wù)承璃。
- 5、worker 線程池會分配獨立線程完成真正的業(yè)務(wù)蚌本,并將結(jié)果返回給handler盔粹。
- 6、handler收到響應(yīng)后程癌,通過send 將結(jié)果返回給client舷嗡。
方案優(yōu)缺點分析:
- 優(yōu)點:可以充分的利用多核cpu 的處理能力。
- 缺點:多線程數(shù)據(jù)共享和訪問比較復(fù)雜嵌莉, reactor 處理所有的事件的監(jiān)聽和響應(yīng)进萄,在單線程運行, 在高并發(fā)場景容易出現(xiàn)性能瓶頸锐峭。
5.1中鼠、基于線程池的改進
在線程Reactor模式基礎(chǔ)上,做如下改進:
- 1只祠、將Handler處理器的執(zhí)行放入線程池兜蠕,多線程進行業(yè)務(wù)處理扰肌。
- 2抛寝、而對于Reactor而言,可以仍為單個線程。如果服務(wù)器為多核的CPU盗舰,為充分利用系統(tǒng)資源晶府,可以將Reactor拆分為兩個線程。
一個簡單的圖如下:
5.2钻趋、改進后的完整示意圖
下面的圖川陆,來自于“Scalable IO in Java”,和上面的圖的意思蛮位,差不多较沪,只是更加詳細。Reactor是一條獨立的線程失仁,Hander 處于線程池中執(zhí)行尸曼。
5.3、多線程Reactor的參考代碼
“Scalable IO in Java”萄焦,的多線程Reactor的參考代碼控轿,是基于單線程做一個線程池的改進,改進的Handler的代碼如下:
public class MThreadHandler implements Runnable {
private SocketChannel channel;
private SelectionKey selectionKey;
ByteBuffer input = ByteBuffer.allocate(1024);
ByteBuffer output = ByteBuffer.allocate(1024);
static final int READING = 0, SENDING = 1;
int state = READING;
ExecutorService pool = Executors.newFixedThreadPool(2);
static final int PROCESSING = 3;
public MThreadHandler(Selector selector, SocketChannel c) throws IOException {
this.channel = c;
c.configureBlocking(false);
// Optionally try first read now
this.selectionKey = this.channel.register(selector, 0);
//將Handler作為callback對象
this.selectionKey.attach(this);
//第二步,注冊Read就緒事件
this.selectionKey.interestOps(SelectionKey.OP_READ);
selector.wakeup();
}
private boolean inputIsComplete() {
/* ... */
return false;
}
private boolean outputIsComplete() {
/* ... */
return false;
}
private void process() {
/* ... */
return;
}
public void run() {
try {
if (state == READING) {
read();
} else if (state == SENDING) {
send();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private synchronized void read() throws IOException {
// ...
channel.read(input);
if (inputIsComplete()) {
state = PROCESSING;
//使用線程pool異步執(zhí)行
pool.execute(new Processer());
}
}
private void send() throws IOException {
channel.write(output);
//write完就結(jié)束了, 關(guān)閉select key
if (outputIsComplete()) {
selectionKey.cancel();
}
}
private synchronized void processAndHandOff() {
process();
state = SENDING;
// or rebind attachment
//process完,開始等待write就緒
selectionKey.interestOps(SelectionKey.OP_WRITE);
}
private class Processer implements Runnable {
public void run() {
processAndHandOff();
}
}
}
Reactor 類沒有大的變化拂封,參考前面的代碼茬射。
六、主從 Reactor 多線程
針對單 Reactor 多線程模型中冒签,Reactor 在單線程中運行在抛,高并發(fā)場景下容易成為性能瓶頸,可以讓 Reactor 在多線程中運行镣衡。
方案說明:
- 1霜定、Reactor主線程 MainReactor 對象通過select 監(jiān)聽連接事件, 收到事件后,通過Acceptor 處理連接事件廊鸥。
- 2望浩、當(dāng) Acceptor 處理連接事件后,MainReactor 將連接分配給SubReactor惰说。
- 3磨德、subreactor 將連接加入到連接隊列進行監(jiān)聽,并創(chuàng)建handler進行各種事件處理。
- 4吆视、當(dāng)有新事件發(fā)生時典挑, subreactor 就會調(diào)用對應(yīng)的handler處理。
- 5啦吧、handler 通過read 讀取數(shù)據(jù)您觉,分發(fā)給后面的worker 線程處理。
- 6授滓、worker 線程池分配獨立的worker 線程進行業(yè)務(wù)處理琳水,并返回結(jié)果肆糕。
- 7、handler 收到響應(yīng)的結(jié)果后在孝,再通過send 將結(jié)果返回給client诚啃。
- 8、Reactor 主線程可以對應(yīng)多個Reactor 子線程, 即MainRecator 可以關(guān)聯(lián)多個SubReactor私沮。
Scalable IO in Java 對 Multiple Reactors 的原理圖解
對于多個CPU的機器始赎,為充分利用系統(tǒng)資源,將Reactor拆分為兩部分仔燕。代碼如下:
public class MThreadReactor implements Runnable {
//subReactors集合, 一個selector代表一個subReactor
Selector[] selectors=new Selector[2];
int next = 0;
final ServerSocketChannel serverSocket;
private MThreadReactor(int port) throws IOException {
//Reactor初始化
selectors[0]=Selector.open();
selectors[1]= Selector.open();
serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(port));
//非阻塞
serverSocket.configureBlocking(false);
//分步處理,第一步,接收accept事件
SelectionKey selectionKey = serverSocket.register( selectors[0], SelectionKey.OP_ACCEPT);
//attach callback object, Acceptor
selectionKey.attach(new Acceptor());
}
public void run() {
try {
while (!Thread.interrupted()) {
for (int i = 0; i <2 ; i++) {
selectors[i].select();
Set<SelectionKey> selectionKeys = selectors[i].selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
//Reactor負(fù)責(zé)dispatch收到的事件
dispatch((SelectionKey) (iterator.next()));
}
selectionKeys.clear();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void dispatch(SelectionKey k) {
Runnable r = (Runnable) (k.attachment());
//調(diào)用之前注冊的callback對象
if (r != null) {
r.run();
}
}
class Acceptor { // ...
public synchronized void run() throws IOException {
//主selector負(fù)責(zé)accept
SocketChannel connection = serverSocket.accept();
if (connection != null) {
//選個subReactor去負(fù)責(zé)接收到的connection
new Handler(selectors[next], connection);
}
if (++next == selectors.length) {
next = 0;
}
}
}
}
方案優(yōu)缺點說明:
- 優(yōu)點:父線程與子線程的數(shù)據(jù)交互簡單職責(zé)明確造垛,父線程只需要接收新連接,子線程完成后續(xù)的業(yè)務(wù)處理晰搀。
- 優(yōu)點:父線程與子線程的數(shù)據(jù)交互簡單筋搏,Reactor 主線程只需要把新連接傳給子線程,子線程無需返回數(shù)據(jù)厕隧。
- 缺點:編程復(fù)雜度較高奔脐。
這種模型在許多項目中廣泛使用,包括 Nginx 主從 Reactor 多進程模型吁讨,Memcached 主從多線程髓迎,Netty 主從多線程模型的支持。
七建丧、Reactor編程的優(yōu)點和缺點
優(yōu)點:
1排龄、響應(yīng)快,不必為單個同步時間所阻塞翎朱,雖然Reactor本身依然是同步的橄维;
2、編程相對簡單拴曲,可以最大程度的避免復(fù)雜的多線程及同步問題争舞,并且避免了多線程/進程的切換開銷;
3澈灼、可擴展性竞川,可以方便的通過增加Reactor實例個數(shù)來充分利用CPU資源;
4叁熔、可復(fù)用性委乌,reactor框架本身與具體事件處理邏輯無關(guān),具有很高的復(fù)用性荣回;
缺點:
1遭贸、相比傳統(tǒng)的簡單模型,Reactor增加了一定的復(fù)雜性心软,因而有一定的門檻壕吹,并且不易于調(diào)試除秀。
2、Reactor模式需要底層的Synchronous Event Demultiplexer支持算利,比如Java中的Selector支持,操作系統(tǒng)的select系統(tǒng)調(diào)用支持泳姐,如果要自己實現(xiàn)Synchronous Event Demultiplexer可能不會有那么高效效拭。
3、Reactor模式在IO讀寫數(shù)據(jù)時還是在同一個線程中實現(xiàn)的胖秒,即使使用多個Reactor機制的情況下缎患,那些共享一個Reactor的Channel如果出現(xiàn)一個長時間的數(shù)據(jù)讀寫,會影響這個Reactor中其他Channel的相應(yīng)時間阎肝,比如在大文件傳輸時挤渔,IO操作就會影響其他Client的相應(yīng)時間,因而對這種操作风题,使用傳統(tǒng)的Thread-Per-Connection或許是一個更好的選擇判导,或則此時使用改進版的Reactor模式如Proactor模式。
參考:
https://www.cnblogs.com/crazymakercircle/p/9833847.html
https://www.cnblogs.com/549294286/p/11241357.html
https://www.ancii.com/aax4jlaz/
http://www.softtest.com/index.php?m=content&c=index&a=show&catid=94&id=13513