一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Netty源碼解讀(四)Netty與Reactor模式 | 并發(fā)編程網(wǎng)

 知識存儲館 2014-05-21

Reactors

一:Netty、NIO、多線程?

時(shí)隔很久終于又更新了!之前一直遲遲未動也是因?yàn)榉e累不夠,后面比較難下手。過年期間@李林鋒hw發(fā)布了一個Netty5.0架構(gòu)剖析和源碼解讀 ,看完也是收獲不少。前面的文章我們分析了Netty的結(jié)構(gòu),這次咱們來分析最錯綜復(fù)雜的一部分-Netty中的多線程以及NIO的應(yīng)用。

理清NIO與Netty的關(guān)系之前,我們必須先要來看看Reactor模式。Netty是一個典型的多線程的Reactor模式的使用,理解了這部分,在宏觀上理解Netty的NIO及多線程部分就不會有什么困難了。

本篇文章依然針對Netty 3.7,不過因?yàn)橐部催^一點(diǎn)Netty 5的源碼,所以會有一點(diǎn)介紹。

 

二:Reactor,反應(yīng)堆還是核電站?

1、Reactor的由來

Reactor是一種廣泛應(yīng)用在服務(wù)器端開發(fā)的設(shè)計(jì)模式。Reactor中文大多譯為“反應(yīng)堆”,我當(dāng)初接觸這個概念的時(shí)候,就感覺很厲害,是不是它的原理就跟“核反應(yīng)”差不多?后來才知道其實(shí)沒有什么關(guān)系,從Reactor的兄弟“Proactor”(多譯為前攝器)就能看得出來,這兩個詞的中文翻譯其實(shí)都不是太好,不夠形象。實(shí)際上,Reactor模式又有別名“Dispatcher”或者“Notifier”,我覺得這兩個都更加能表明它的本質(zhì)。

那么,Reactor模式究竟是個什么東西呢?這要從事件驅(qū)動的開發(fā)方式說起。我們知道,對于應(yīng)用服務(wù)器,一個主要規(guī)律就是,CPU的處理速度是要遠(yuǎn)遠(yuǎn)快于IO速度的,如果CPU為了IO操作(例如從Socket讀取一段數(shù)據(jù))而阻塞顯然是不劃算的。好一點(diǎn)的方法是分為多進(jìn)程或者線程去進(jìn)行處理,但是這樣會帶來一些進(jìn)程切換的開銷,試想一個進(jìn)程一個數(shù)據(jù)讀了500ms,期間進(jìn)程切換到它3次,但是CPU卻什么都不能干,就這么切換走了,是不是也不劃算?

這時(shí)先驅(qū)們找到了事件驅(qū)動,或者叫回調(diào)的方式,來完成這件事情。這種方式就是,應(yīng)用業(yè)務(wù)向一個中間人注冊一個回調(diào)(event handler),當(dāng)IO就緒后,就這個中間人產(chǎn)生一個事件,并通知此handler進(jìn)行處理。這種回調(diào)的方式,也體現(xiàn)了“好萊塢原則”(Hollywood principle)-“Don’t call us, we’ll call you”,在我們熟悉的IoC中也有用到??磥碥浖_發(fā)真是互通的!

好了,我們現(xiàn)在來看Reactor模式。在前面事件驅(qū)動的例子里有個問題:我們?nèi)绾沃繧O就緒這個事件,誰來充當(dāng)這個中間人?Reactor模式的答案是:由一個不斷等待和循環(huán)的單獨(dú)進(jìn)程(線程)來做這件事,它接受所有handler的注冊,并負(fù)責(zé)先操作系統(tǒng)查詢IO是否就緒,在就緒后就調(diào)用指定handler進(jìn)行處理,這個角色的名字就叫做Reactor。

2、Reactor與NIO

Java中的NIO可以很好的和Reactor模式結(jié)合。關(guān)于NIO中的Reactor模式,我想沒有什么資料能比Doug Lea大神(不知道Doug Lea?看看JDK集合包和并發(fā)包的作者吧)在《Scalable IO in Java》解釋的更簡潔和全面了。NIO中Reactor的核心是Selector,我寫了一個簡單的Reactor示例,這里我貼一個核心的Reactor的循環(huán)(這種循環(huán)結(jié)構(gòu)又叫做EventLoop),剩余代碼在這里

01public void run() {
02    try {
03        while (!Thread.interrupted()) {
04            selector.select();
05            Set selected = selector.selectedKeys();
06            Iterator it = selected.iterator();
07            while (it.hasNext())
08                dispatch((SelectionKey) (it.next()));
09            selected.clear();
10        }
11    } catch (IOException ex) { /* ... */
12    }
13}

3、與Reactor相關(guān)的其他概念

前面提到了Proactor模式,這又是什么呢?簡單來說,Reactor模式里,操作系統(tǒng)只負(fù)責(zé)通知IO就緒,具體的IO操作(例如讀寫)仍然是要在業(yè)務(wù)進(jìn)程里阻塞的去做的,而Proactor模式則更進(jìn)一步,由操作系統(tǒng)將IO操作執(zhí)行好(例如讀取,會將數(shù)據(jù)直接讀到內(nèi)存buffer中),而handler只負(fù)責(zé)處理自己的邏輯,真正做到了IO與程序處理異步執(zhí)行。所以我們一般又說Reactor是同步IO,Proactor是異步IO。

關(guān)于阻塞和非阻塞、異步和非異步,以及UNIX底層的機(jī)制,大家可以看看這篇文章IO – 同步,異步,阻塞,非阻塞 (亡羊補(bǔ)牢篇),以及陶輝(《深入理解nginx》的作者)《高性能網(wǎng)絡(luò)編程》的系列。

三:由Reactor出發(fā)來理解Netty

1、多線程下的Reactor

講了一堆Reactor,我們回到Netty。在《Scalable IO in Java》中講到了一種多線程下的Reactor模式。在這個模式里,mainReactor只有一個,負(fù)責(zé)響應(yīng)client的連接請求,并建立連接,它使用一個NIO Selector;subReactor可以有一個或者多個,每個subReactor都會在一個獨(dú)立線程中執(zhí)行,并且維護(hù)一個獨(dú)立的NIO Selector。

這樣的好處很明顯,因?yàn)閟ubReactor也會執(zhí)行一些比較耗時(shí)的IO操作,例如消息的讀寫,使用多個線程去執(zhí)行,則更加有利于發(fā)揮CPU的運(yùn)算能力,減少IO等待時(shí)間。

Multiple Reactors

2、Netty中的Reactor與NIO

好了,了解了多線程下的Reactor模式,我們來看看Netty吧(以下部分主要針對NIO,OIO部分更加簡單一點(diǎn),不重復(fù)介紹了)。Netty里對應(yīng)mainReactor的角色叫做“Boss”,而對應(yīng)subReactor的角色叫做”Worker”。Boss負(fù)責(zé)分配請求,Worker負(fù)責(zé)執(zhí)行,好像也很貼切!以TCP的Server端為例,這兩個對應(yīng)的實(shí)現(xiàn)類分別為NioServerBossNioWorker(Server和Client的Worker沒有區(qū)別,因?yàn)榻⑦B接之后,雙方就是對等的進(jìn)行傳輸了)。

Netty 3.7中Reactor的EventLoop在AbstractNioSelector.run()中,它實(shí)現(xiàn)了Runnable接口。這個類是Netty NIO部分的核心。它的邏輯非常復(fù)雜,其中還包括一些對JDK Bug的處理(例如rebuildSelector),剛開始讀的時(shí)候不需要深入那么細(xì)節(jié)。我精簡了大部分代碼,保留主干如下:

01abstract class AbstractNioSelector implements NioSelector {
02 
03    //NIO Selector
04    protected volatile Selector selector;
05 
06    //內(nèi)部任務(wù)隊(duì)列
07    private final Queue taskQueue = new ConcurrentLinkedQueue();
08 
09    //selector循環(huán)
10    public void run() {
11        for (;;) {
12            try {
13                //處理內(nèi)部任務(wù)隊(duì)列
14                processTaskQueue();
15                //處理selector事件對應(yīng)邏輯
16                process(selector);
17            } catch (Throwable t) {
18                try {
19                    Thread.sleep(1000);
20                } catch (InterruptedException e) {
21                    // Ignore.
22                }
23            }
24        }
25    }
26 
27    private void processTaskQueue() {
28        for (;;) {
29            final Runnable task = taskQueue.poll();
30            if (task == null) {
31                break;
32            }
33            task.run();
34        }
35    }
36 
37    protected abstract void process(Selector selector) throws IOException;

其中process是主要的處理事件的邏輯,例如在AbstractNioWorker中,處理邏輯如下:

01protected void process(Selector selector) throws IOException {
02    Set selectedKeys = selector.selectedKeys();
03    if (selectedKeys.isEmpty()) {
04        return;
05    }
06    for (Iterator i = selectedKeys.iterator(); i.hasNext();) {
07        SelectionKey k = i.next();
08        i.remove();
09        try {
10            int readyOps = k.readyOps();
11            if ((readyOps & SelectionKey.OP_READ) != 0 || readyOps == 0) {
12                if (!read(k)) {
13                    // Connection already closed - no need to handle write.
14                    continue;
15                }
16            }
17            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
18                writeFromSelectorLoop(k);
19            }
20        } catch (CancelledKeyException e) {
21            close(k);
22        }
23 
24        if (cleanUpCancelledKeys()) {
25            break; // break the loop to avoid ConcurrentModificationException
26        }
27    }
28}

這不就是第二部分提到的selector經(jīng)典用法了么?

在4.0之后,作者覺得NioSelector這個叫法,以及區(qū)分NioBossNioWorker的做法稍微繁瑣了點(diǎn),干脆就將這些合并成了NioEventLoop,從此這兩個角色就不做區(qū)分了。我倒是覺得新版本的會更優(yōu)雅一點(diǎn)。

3、Netty中的多線程

下面我們來看Netty的多線程部分。一旦對應(yīng)的Boss或者Worker啟動,就會分配給它們一個線程去一直執(zhí)行。對應(yīng)的概念為BossPoolWorkerPool。對于每個NioServerSocketChannel,Boss的Reactor有一個線程,而Worker的線程數(shù)由Worker線程池大小決定,但是默認(rèn)最大不會超過CPU核數(shù)*2,當(dāng)然,這個參數(shù)可以通過NioServerSocketChannelFactory構(gòu)造函數(shù)的參數(shù)來設(shè)置。

1public NioServerSocketChannelFactory(
2        Executor bossExecutor, Executor workerExecutor,
3        int workerCount) {
4    this(bossExecutor, 1, workerExecutor, workerCount);
5}

最后我們比較關(guān)心一個問題,我們之前ChannlePipeline中的ChannleHandler是在哪個線程執(zhí)行的呢?答案是在Worker線程里執(zhí)行的,并且會阻塞Worker的EventLoop。例如,在NioWorker中,讀取消息完畢之后,會觸發(fā)MessageReceived事件,這會使得Pipeline中的handler都得到執(zhí)行。

01protected boolean read(SelectionKey k) {
02    ....
03 
04    if (readBytes > 0) {
05        // Fire the event.
06        fireMessageReceived(channel, buffer);
07    }
08 
09    return true;
10}

可以看到,對于處理事件較長的業(yè)務(wù),并不太適合直接放到ChannelHandler中執(zhí)行。那么怎么處理呢?我們在Handler部分會進(jìn)行介紹。

最后附上項(xiàng)目github地址,歡迎交流:https://github.com/code4craft/netty-learning

參考資料:

題圖來自:http://www./france-gives-green-light-to-tokamak-fusion-reactor/

(全文完)如果您喜歡此文請點(diǎn)贊,分享,評論。





1

code4craft

黃億華,大眾點(diǎn)評網(wǎng)工程師,興趣點(diǎn):web開發(fā)、爬蟲及網(wǎng)絡(luò)編程。

您可能感興趣的文章

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    国产欧美亚洲精品自拍| 99久久精品午夜一区| 精品日韩国产高清毛片| 亚洲一区二区三区四区| 国产精品涩涩成人一区二区三区 | 国产精品一区二区不卡中文 | 色综合久久六月婷婷中文字幕| 国产精品视频久久一区| 在线观看视频成人午夜| 91熟女大屁股偷偷对白| 冬爱琴音一区二区中文字幕| 麻豆视传媒短视频在线看| 最好看的人妻中文字幕| 五月激情婷婷丁香六月网| 国产亚洲欧美一区二区| 黄色污污在线免费观看| 黄色污污在线免费观看| 乱女午夜精品一区二区三区| 国产亚洲精品久久99| 免费观看一区二区三区黄片| 日韩欧美国产亚洲一区| 九九热精彩视频在线免费| 亚洲精品伦理熟女国产一区二区 | 人妻内射在线二区一区| 九九热这里有精品20| 色一情一伦一区二区三| 人妻一区二区三区多毛女| 国产av熟女一区二区三区四区| 精品偷拍一区二区三区| 激情爱爱一区二区三区| 欧美日韩在线观看自拍| 日本福利写真在线观看| 视频在线免费观看你懂的| 国产二级一级内射视频播放| 国产伦精品一区二区三区高清版| 九九热精彩视频在线播放| 欧美一级特黄大片做受大屁股| 欧美大粗爽一区二区三区| 国产精品亚洲精品亚洲| 欧美一级特黄特色大色大片| 91麻豆精品欧美视频|