Reactor設(shè)計(jì)模式

一. 為什么需要

解決多請(qǐng)求問題,但是這些請(qǐng)求不需要一直占有整個(gè)線程資源(比如IO操作時(shí)不必一直等待),所以不適合使用一個(gè)請(qǐng)求分配一個(gè)線程的多線程方案;類似于消息隊(duì)列模型,但是是事件驅(qū)動(dòng)痴腌,沒有Queue來做緩沖;優(yōu)點(diǎn):解耦燃领、高效士聪、提高復(fù)用,缺點(diǎn):需要操作系統(tǒng)底層支持猛蔽、內(nèi)部回調(diào)復(fù)雜剥悟。

二. 預(yù)備知識(shí)

IO操作主要分成兩部分:

  1. 數(shù)據(jù)準(zhǔn)備,將數(shù)據(jù)從磁盤加載到內(nèi)核緩存
  2. 將數(shù)據(jù)從內(nèi)核緩存加載到用戶緩存

2.1 IO的4種模型

  • 阻塞曼库、非阻塞(等待數(shù)據(jù)全部讀取成功再返回区岗,還是讀取為空馬上返回然后下次再讀)
  • 同步、異步(用戶緩存主動(dòng)去讀取內(nèi)核緩存毁枯,還是內(nèi)核緩存讀取磁盤成功后通知用戶緩存)
  • NIO是同步非阻塞模型慈缔,也是IO多路復(fù)用基礎(chǔ)
  • Reactor模式基于同步I/O,Proactor模式基于異步I/O

2.2 IO多路復(fù)用

區(qū)別于傳統(tǒng)的多進(jìn)程并發(fā)模型 (每有新的IO流就分配一個(gè)新的進(jìn)程管理)种玛,IO多路復(fù)用僅使用單個(gè)線程藐鹤,通過記錄跟蹤每個(gè)I/O流的狀態(tài)來同時(shí)管理多個(gè)I/O流(哪個(gè)IO流ready線程就處理哪個(gè))

select, poll, epoll 都是I/O多路復(fù)用的具體的實(shí)現(xiàn):
select:僅返回有無事件不返回具體事件Id,只能監(jiān)控1024個(gè)連接赂韵,線程不安全
poll:連接數(shù)無限制
epoll:返回具體事件Id娱节,線程安全

三. 反應(yīng)器模式

處理一個(gè)或多個(gè)客戶端并發(fā)請(qǐng)求服務(wù)的事件設(shè)計(jì)模式。當(dāng)請(qǐng)求抵達(dá)后右锨,服務(wù)處理程序使用I/O多路復(fù)用策略,然后同步地派發(fā)這些請(qǐng)求至相關(guān)的請(qǐng)求處理程序碌秸。

Reactor_Structures.png

3.1 模塊組成

包括5個(gè)模塊:

  • Handle:事件(網(wǎng)絡(luò)編程中就是一個(gè)Socket绍移,數(shù)據(jù)庫(kù)操作中就是一個(gè)DBConnection,Java NIO中的Channel)
  • EventHandler:事件處理器讥电,用于處理不同狀態(tài)的事件
  • Concrete Event Handler:事件處理器的具體實(shí)現(xiàn)蹂窖,實(shí)現(xiàn)了事件處理器所提供的各種回調(diào)方法,從而實(shí)現(xiàn)特定于業(yè)務(wù)的邏輯
  • Synchronous Event Demultiplexer:用于等待事件的發(fā)生恩敌,調(diào)用方在調(diào)用它的時(shí)候會(huì)被阻塞瞬测,一直阻塞到同步事件分離器上有事件產(chǎn)生為止(NIO中對(duì)應(yīng)Selector,當(dāng)Selector.select()返回時(shí)說明有事件發(fā)生,然后調(diào)用Selector的selectedKeys()方法獲取Set<SelectionKey>月趟,一個(gè)SelectionKey表示一個(gè)有事件發(fā)生的Channel以及該Channel上的事件類型)
  • Initiation Dispatcher:用于管理EventHandler灯蝴、分發(fā)event。通過Synchronous Event Demultiplexer來等待事件的發(fā)生孝宗,一旦事件發(fā)生穷躁,Initiation Dispatcher首先會(huì)分離出每一個(gè)事件,然后調(diào)用事件處理器因妇,最后調(diào)用相關(guān)的回調(diào)方法來處理這些事件

3.2 運(yùn)行流程

  1. 初始化dispatcher问潭,注冊(cè)具體事件處理器到分發(fā)器(即指定什么事件觸發(fā)什么事件處理器)
  2. 注冊(cè)完畢后,分發(fā)器調(diào)用handle_events方法啟動(dòng)事件循環(huán)婚被,并啟動(dòng)Synchronous Event Demultiplexer等待事件發(fā)生(阻塞等待)
  3. 當(dāng)有事件發(fā)生狡忙,即某個(gè)Handle變?yōu)閞eady狀態(tài)(如TCP socket變?yōu)榈却x狀態(tài)),Synchronous Event Demultiplexer就會(huì)通知Initiation Dispatcher
  4. Initiation Dispatcher根據(jù)發(fā)生的事件址芯,將被事件源激活的Handle作為『key』來尋找并分發(fā)恰當(dāng)?shù)氖录幚砥骰卣{(diào)方法

3.3 具體模型分類

  • 單線程模型(I/O灾茁、非I/O業(yè)務(wù)操作都在一個(gè)線程上處理,可能會(huì)大大延遲I/O請(qǐng)求的響應(yīng))
  • 工作站線程池模型(非I/O操作從Reactor線程中移出轉(zhuǎn)交給工作者線程池執(zhí)行)
  • 多線程模型(mainReactor線程主要負(fù)責(zé)接收客戶端的連接請(qǐng)求是复,然后將接收到的SocketChannel傳遞給subReactor删顶,由subReactor來完成和客戶端的通信),但是注意subReactor線程只負(fù)責(zé)完成I/O的read()或者write()操作淑廊,在讀取到數(shù)據(jù)后業(yè)務(wù)邏輯的處理仍然放入到工作者線程池中完成逗余,可避免因?yàn)閞ead()數(shù)據(jù)量太大而導(dǎo)致后面的客戶端連接請(qǐng)求得不到即時(shí)處理的情況
singleReactor.png
workerThreadPool.png
multipleReactors.png

四. 源碼分析

高性能NIO框架netty、騰訊開源RPC框架Tars的NIO模型都是很典型的Reactor設(shè)計(jì)模式季惩,下面以Tars源碼來分析Reactor模式的java NIO實(shí)現(xiàn)(僅展示關(guān)鍵實(shí)現(xiàn))录粱。

package com.qq.tars.net.core.nio;
tarsNIO.PNG

4.1 Reactor

import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SelectableChannel;

public final class Reactor extends Thread {

    protected volatile Selector selector = null;
    private Acceptor acceptor = null;

    //啟動(dòng)
    public Reactor(SelectorManager selectorManager) throws IOException {
        this.acceptor = new TCPAcceptor(selectorManager);
        this.selector = Selector.open();
    }

    //注冊(cè)
    public void registerChannel(SelectableChannel channel, int ops, Object attachment) throws IOException {

        SelectionKey key = channel.register(this.selector, ops, attachment);
    }

    //循環(huán)事件
    public void run() {

            for (;;) {
                selector.select();
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    dispatchEvent(key);
                }
            }
        
    }

    //處理事件
    private void dispatchEvent(final SelectionKey key) throws IOException {
        if (key.isConnectable()) {
            acceptor.handleConnectEvent(key);
        } else if (key.isAcceptable()) {
            acceptor.handleAcceptEvent(key);
        } else if (key.isReadable()) {
            acceptor.handleReadEvent(key);
        } else if (key.isValid() && key.isWritable()) {
            acceptor.handleWriteEvent(key);
        }
    }

}

4.2 TCPAcceptor
處理不同事件,以處理connect画拾、read事件為例:

    public void handleConnectEvent(SelectionKey key) throws IOException {
        //1. Get the client channel
        SocketChannel client = (SocketChannel) key.channel();

        //2. Set the session status
        TCPSession session = (TCPSession) key.attachment();
        if (session == null) throw new RuntimeException("The session is null when connecting to ...");

        //3. Connect to server
        try {
            client.finishConnect();
            key.interestOps(SelectionKey.OP_READ);
            session.setStatus(SessionStatus.CLIENT_CONNECTED);
        } finally {
            session.finishConnect();
        }
    }

    public void handleReadEvent(SelectionKey key) throws IOException {
        TCPSession session = (TCPSession) key.attachment();
        if (session == null) throw new RuntimeException("The session is null when reading data...");
        session.read();
    }

4.3 TCPSession
以read事件的readResponse方法為例:

//放入工作線程池
response = selectorManager.getProtocolFactory().getDecoder().decodeResponse(tempBuffer, this);
selectorManager.getThreadPool().execute(new WorkThread(response, selectorManager));

4.4 工作線程池
SelectorManager提供線程池啥繁,WorkThread具體進(jìn)行業(yè)務(wù)處理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市青抛,隨后出現(xiàn)的幾起案子旗闽,更是在濱河造成了極大的恐慌,老刑警劉巖蜜另,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件适室,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡举瑰,警方通過查閱死者的電腦和手機(jī)捣辆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來此迅,“玉大人汽畴,你說我怎么就攤上這事旧巾。” “怎么了忍些?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵鲁猩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我坐昙,道長(zhǎng)绳匀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任炸客,我火速辦了婚禮疾棵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痹仙。我一直安慰自己是尔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布开仰。 她就那樣靜靜地躺著拟枚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪众弓。 梳的紋絲不亂的頭發(fā)上恩溅,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音谓娃,去河邊找鬼脚乡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滨达,可吹牛的內(nèi)容都是我干的奶稠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捡遍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼锌订!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起画株,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤辆飘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后谓传,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜈项,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年良拼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了战得。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片充边。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡庸推,死狀恐怖常侦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贬媒,我是刑警寧澤聋亡,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站际乘,受9級(jí)特大地震影響坡倔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脖含,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一罪塔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧养葵,春花似錦征堪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至着绊,卻和暖如春谐算,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背归露。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工洲脂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人靶擦。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓腮考,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親玄捕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子踩蔚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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

  • 本文是Netty文集中“Netty 那些事兒”系列的文章。主要結(jié)合在開發(fā)實(shí)戰(zhàn)中枚粘,我們遇到的一些“奇奇怪怪”的問題馅闽,...
    tomas家的小撥浪鼓閱讀 15,503評(píng)論 3 35
  • 何為Reactor線程模型? Reactor模式是事件驅(qū)動(dòng)的馍迄,有一個(gè)或多個(gè)并發(fā)輸入源福也,有一個(gè)Service Han...
    未名枯草閱讀 3,500評(píng)論 2 11
  • NIO(Non-blocking I/O,在Java領(lǐng)域攀圈,也稱為New I/O)暴凑,是一種同步非阻塞的I/O模型,也...
    閃電是只貓閱讀 3,116評(píng)論 0 7
  • Java I/O模型 同步 vs. 異步 同步I/O每個(gè)請(qǐng)求必須逐個(gè)地被處理赘来,一個(gè)請(qǐng)求的處理會(huì)導(dǎo)致整個(gè)流程的暫時(shí)等...
    東升的思考閱讀 1,599評(píng)論 1 1
  • 時(shí)光回到二十多年前现喳。夏日凯傲,傍晚,小胡同兒里嗦篱。我和小影在玩兒跳房子冰单,碰見了要去山腳下散步的敏姐。敏姐當(dāng)時(shí)在城...
    籬落0186閱讀 403評(píng)論 0 2