Java NIO——Reactor模式

前言——服務(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沛硅,一起剝皮案震驚了整個濱河市眼刃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摇肌,老刑警劉巖擂红,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異围小,居然都是意外死亡昵骤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門肯适,熙熙樓的掌柜王于貴愁眉苦臉地迎上來变秦,“玉大人,你說我怎么就攤上這事框舔“樗ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵雨饺,是天一觀的道長钳垮。 經(jīng)常有香客問我,道長额港,這世上最難降的妖魔是什么饺窿? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮移斩,結(jié)果婚禮上肚医,老公的妹妹穿的比我還像新娘绢馍。我一直安慰自己,他們只是感情好肠套,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布舰涌。 她就那樣靜靜地躺著,像睡著了一般你稚。 火紅的嫁衣襯著肌膚如雪瓷耙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天刁赖,我揣著相機與錄音搁痛,去河邊找鬼。 笑死宇弛,一個胖子當(dāng)著我的面吹牛鸡典,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播枪芒,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼彻况,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舅踪?” 一聲冷哼從身側(cè)響起疗垛,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硫朦,沒想到半個月后贷腕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡咬展,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年泽裳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片破婆。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡涮总,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祷舀,到底是詐尸還是另有隱情瀑梗,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布裳扯,位于F島的核電站抛丽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏饰豺。R本人自食惡果不足惜亿鲜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冤吨。 院中可真熱鬧蒿柳,春花似錦饶套、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至圾叼,卻和暖如春蛤克,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背褐奥。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留翘簇,地道東北人撬码。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像版保,于是被迫代替她去往敵國和親呜笑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Reactor 反應(yīng)器設(shè)計模式(Reactor pattern)是一種為處理并發(fā)服務(wù)請求彻犁,并將請求提交到一個或者多...
    煙雨亂平生閱讀 307評論 0 0
  • ?NIO(Non-blocking I/O)叫胁,非阻塞的I/O模型。自java1.4版本之后推出汞幢,為什么在已有I/O...
    挪威的senlin閱讀 557評論 0 0
  • ??NIO(Non-blocking I/O驼鹅,在Java領(lǐng)域,也稱為New I/O)森篷,是一種同步非阻塞的I/O模型...
    Skymiles閱讀 472評論 0 0
  • 1输钩、基礎(chǔ)I/O模型 在《UNIX網(wǎng)絡(luò)編程》中介紹了5中I/O模型:阻塞I/O、非阻塞I/O仲智、I/O復(fù)用买乃、SIGIO...
    橋頭放牛娃閱讀 3,561評論 3 14
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友钓辆。感恩相遇剪验!感恩不離不棄。 中午開了第一次的黨會前联,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,562評論 0 11