Netty 線程模型

一、單線程Reactor模式

????????Netty線程模型總體上可以說(shuō)是Reactor模式的一種變種,我們先看看什么是Reactor模式。這里主要參考維基百科上對(duì)Ractor的定義與描述。

????????Reactor模式是一種事件處理模式,單個(gè)或多個(gè)事件(Event)并發(fā)地投遞到事件處理服務(wù)(Service Handler)藐俺,事件處理服務(wù)將事件進(jìn)行分離炊甲,同步的將他們分發(fā)到對(duì)應(yīng)的事件處理器中去處理。Reactor模式有下面幾種參與者:

? ? ? ? 1欲芹、資源:任何提供系統(tǒng)的輸入或者消費(fèi)系統(tǒng)的輸出的資源卿啡,如:Socket句柄。

? ? ? ? 2菱父、同步事件分離器:通常使用event loop來(lái)進(jìn)行對(duì)資源的阻塞等待颈娜,當(dāng)有資源就緒的時(shí)候事件分離器將資源傳遞給事件分發(fā)器。

? ? ? ? 3浙宜、事件分發(fā)器:處理請(qǐng)求處理器的注冊(cè)或者反注冊(cè)官辽,將資源從時(shí)間分離器分發(fā)到資源對(duì)應(yīng)的請(qǐng)求處理器中同步執(zhí)行。

? ? ? ? 4粟瞬、請(qǐng)求處理器:應(yīng)用定義的對(duì)相關(guān)資源的請(qǐng)求處理同仆。

? ??????下面用一張圖表示通用Reactor模式的示意圖:

Reactor模式

????????Reactor模式的優(yōu)點(diǎn)與缺點(diǎn):

????????Reactor模式使得應(yīng)用代碼和Reactor實(shí)現(xiàn)相分離,這使得用戶(hù)可以將應(yīng)用代碼設(shè)計(jì)成最大程度可復(fù)用的模塊裙品,由于對(duì)于請(qǐng)求處理器的調(diào)用的是同步的俗批,用戶(hù)不需要去考慮并發(fā)問(wèn)題,同時(shí)也減少了多線程對(duì)系統(tǒng)資源的消耗市怎。另一方面扶镀,相比于過(guò)程化模式的程序,Reactor模式下的程序相對(duì)比較難于Debug焰轻,同時(shí)單線程的設(shè)計(jì)在多核時(shí)代不能夠充分利用多核處理器資源,影響了系統(tǒng)的擴(kuò)展性昆雀。

????????這是最簡(jiǎn)單的單線程Reactor模式辱志,網(wǎng)上也有對(duì)于多線程Reactor模式的一些介紹,本文不做過(guò)多介紹狞膘,多線程Reactor模式也是在原有的模型基礎(chǔ)上進(jìn)行的變種揩懒。

二、Netty線程模型

????????Netty是一款高效的NIO框架和工具挽封,基于JAVA NIO提供的API實(shí)現(xiàn)已球。在JAVA NIO方面Selector給Reactor模式提供了基礎(chǔ),Netty結(jié)合Selector和Reactor模式設(shè)計(jì)了高效的線程模型辅愿,Reactor模式的參與者主要有下面一些組件:

????????Selector

? ? ? ? EventLoopGroup/EventLoop

? ? ? ? ChannelPipeline

????????下面對(duì)其功能和其在Netty之Reactor模式中扮演的角色進(jìn)行介紹智亮。

1、Selector

????????Selector是JAVA NIO提供的SelectableChannel多路復(fù)用器点待,它內(nèi)部維護(hù)著三個(gè)SelectionKey集合阔蛉,負(fù)責(zé)配合select操作將就緒的IO事件分離出來(lái),落地為SelectionKey癞埠。在Netty線程模型中状原,我認(rèn)為Selector充當(dāng)著demultiplexer的角色聋呢,而對(duì)于SelectionKey我們可以將它看成Reactor模式中的資源。

2颠区、EventLoopGroup/EventLoop

????????EventLoopGroup是一組EventLoop的抽象削锰,由于Netty對(duì)Reactor模式進(jìn)行了變種,實(shí)際上為更好的利用多核CPU資源毕莱,Netty實(shí)例中一般會(huì)有多個(gè)EventLoop同時(shí)工作器贩,每個(gè)EventLoop維護(hù)著一個(gè)Selector實(shí)例,類(lèi)似單線程Reactor模式地工作著央串。至于多少線程可有用戶(hù)決定磨澡,Netty也根據(jù)實(shí)際上的處理器核數(shù)提供了一個(gè)默認(rèn)的數(shù)字,我們也建議使用這個(gè)數(shù)字:

private static final int DEFAULT_EVENT_LOOP_THREADS;

static {

????????DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt( "io.netty.eventLoopThreads",? ? ????????Runtime.getRuntime().availableProcessors() * 2));

????????if (logger.isDebugEnabled()) { logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);

????????}

}

????????EventLoopGroup提供next接口质和,可以總一組EventLoop里面按照一定規(guī)則獲取其中一個(gè)EventLoop來(lái)處理任務(wù)稳摄,對(duì)于EventLoopGroup這里需要了解的是在Netty中,在Netty服務(wù)器編程中我們需要BossEventLoopGroup和WorkerEventLoopGroup兩個(gè)EventLoopGroup來(lái)進(jìn)行工作饲宿。通常一個(gè)服務(wù)端口即一個(gè)ServerSocketChannel對(duì)應(yīng)一個(gè)Selector和一個(gè)EventLoop線程厦酬,也就是我們建議BossEventLoopGroup的線程數(shù)參數(shù)這是為1。BossEventLoop負(fù)責(zé)接收客戶(hù)端的連接并將SocketChannel交給WorkerEventLoopGroup來(lái)進(jìn)行IO處理瘫想。下面是他們的工作示意圖:

????????如上圖仗阅,BossEventLoopGroup通常是一個(gè)單線程的EventLoop,EventLoop維護(hù)著一個(gè)注冊(cè)了ServerSocketChannel的Selector實(shí)例国夜,BoosEventLoop不斷輪詢(xún)Selector將連接事件分離出來(lái)减噪,通常是OP_ACCEPT事件,然后將accept得到的SocketChannel交給WorkerEventLoopGroup车吹,WorkerEventLoopGroup會(huì)由next選擇其中一個(gè)EventLoopGroup來(lái)將這個(gè)SocketChannel注冊(cè)到其維護(hù)的Selector并對(duì)其后續(xù)的IO事件進(jìn)行處理筹裕。在Reactor模式中BossEventLoopGroup主要是對(duì)多線程的擴(kuò)展,而每個(gè)EventLoop的實(shí)現(xiàn)涵蓋IO事件的分離窄驹,和分發(fā)(Dispatcher)朝卒。

3、ChannelPipeline

????????在Netty中ChannelPipeline維護(hù)著一個(gè)ChannelHandler的鏈表隊(duì)列乐埠,每個(gè)SocketChannel都有一個(gè)維護(hù)著一個(gè)ChannelPipeline實(shí)例抗斤,而每個(gè)ChannelPipeline實(shí)例通常維護(hù)著一個(gè)ChannelHandler鏈表隊(duì)列,由于SocketChannel是和SelectionKey關(guān)聯(lián)的丈咐,也就是Reactor模式中的資源瑞眼,當(dāng)EventLoop將SelectionKey分離出來(lái)的時(shí)候會(huì)將SelectionKey關(guān)聯(lián)的Channel交給Channel關(guān)聯(lián)的ChannelHandler鏈來(lái)處理,那么ChannelPipeline其實(shí)是擔(dān)任著Reactor模式中的請(qǐng)求處理器這個(gè)角色棵逊。既然提到ChannelPipeline负拟,這里對(duì)其也進(jìn)行一些簡(jiǎn)單的介紹吧。

????????ChannelPipeline的默認(rèn)實(shí)現(xiàn)是DefaultChannelPipeline歹河,DefaultChannelPipeline本身維護(hù)著一個(gè)用戶(hù)不可見(jiàn)的tail和head的ChannelHandler掩浙,他們分別位于鏈表隊(duì)列的頭部和尾部花吟。tail在更上從的部分,而head在靠近網(wǎng)絡(luò)層的方向厨姚。在Netty中關(guān)于ChannelHandler有兩個(gè)重要的接口衅澈,ChannelInBoundHandler和ChannelOutBoundHandler。inbound可以理解為網(wǎng)絡(luò)數(shù)據(jù)從外部流向系統(tǒng)內(nèi)部谬墙,而outbound可以理解為網(wǎng)絡(luò)數(shù)據(jù)從系統(tǒng)內(nèi)部流向系統(tǒng)外部今布。用戶(hù)實(shí)現(xiàn)的ChannelHandler可以根據(jù)需要實(shí)現(xiàn)其中一個(gè)或多個(gè)接口,將其放入Pipeline中的鏈表隊(duì)列中拭抬,ChannelPipeline會(huì)根據(jù)不同的IO事件類(lèi)型來(lái)找到相應(yīng)的Handler來(lái)處理部默,同時(shí)鏈表隊(duì)列是責(zé)任鏈模式的一種變種,自上而下或自下而上所有滿(mǎn)足事件關(guān)聯(lián)的Handler都會(huì)對(duì)事件進(jìn)行處理造虎。

????????上面部分主要是對(duì)比Reactor模式對(duì)Netty的線程模型進(jìn)行相應(yīng)的對(duì)比介紹傅蹂,下面主要會(huì)結(jié)合JavaScript單線程模型多介紹一些Netty對(duì)EventLoop的實(shí)現(xiàn)及相應(yīng)的思考。

4算凿、JavaScript單線程模型

????????眾所周知份蝴,JavaScript是單線程的,也就是任何時(shí)刻同時(shí)只能有一個(gè)線程堆棧在執(zhí)行氓轰,那么對(duì)于下面這段代碼可能有同學(xué)會(huì)疑惑這婚夫,這個(gè)是怎么執(zhí)行的:

console.log("A");

setTimeout(function timeout() {

????????console.log("B");

}, 10);

console.log("C"); ....

//biz code console.log("D");

????????最初的想法是我們?cè)O(shè)置了一個(gè)定時(shí)任務(wù),10ms之后執(zhí)行署鸡,如果在biz code處的code需要執(zhí)行20ms以上案糙,那么timeout怎么能夠順利執(zhí)行呢,而且單線程是如何做到既執(zhí)行下面的biz code又執(zhí)行timeout的呢靴庆。事實(shí)上如果biz code的部分如果執(zhí)行時(shí)間大于10ms侍筛,那么timeout并不會(huì)立即準(zhǔn)時(shí)執(zhí)行的。要明白其中的原因撒穷,我們可以從一張圖來(lái)理解JavaScript的單線程模型:

????????首先簡(jiǎn)單理解下eventloop機(jī)制,即一個(gè)線程在執(zhí)行完主線程后會(huì)不斷輪詢(xún)callback隊(duì)列裆熙,取出就緒任務(wù)執(zhí)行端礼,每個(gè)循環(huán)稱(chēng)為一個(gè)tick。因?yàn)镴avaScript只有一個(gè)線程執(zhí)行入录,因此也只有一個(gè)線程堆棧蛤奥,結(jié)合上面的code實(shí)例接單說(shuō)明一下對(duì)應(yīng)堆棧的變動(dòng):

????????console.log("A")入棧執(zhí)行,輸出"A"僚稿,console.log("A")出棧凡桥。setTimeout入棧,WebAPIs后臺(tái)不斷檢查timeout對(duì)象的超時(shí)時(shí)間是否已經(jīng)到達(dá)蚀同,如果到達(dá)則會(huì)將對(duì)于的callback也即timeout放入callback隊(duì)列缅刽。接下來(lái)console.log("C")會(huì)入棧執(zhí)行啊掏,輸出"C",然后出棧衰猛。...最后console.log("D")會(huì)入棧執(zhí)行迟蜜,輸出"D",然后出棧啡省。主區(qū)域代碼執(zhí)行完畢線程會(huì)不斷輪詢(xún)callback隊(duì)列來(lái)查詢(xún)是否有就緒callback娜睛,如果有則取出執(zhí)行,如果沒(méi)有則繼續(xù)輪詢(xún)卦睹。而對(duì)于超時(shí)或者是我們使用ajax的callback畦戒,后臺(tái)會(huì)根據(jù)IO操作或超時(shí)時(shí)間是否完畢來(lái)決定是否將callback放入callback隊(duì)列,這就是EventLoop機(jī)制结序。Node的單線程EventLoop模型相比于JavaScript的單線程EventLoop模型類(lèi)似障斋,但是更復(fù)雜一些,整體模型可以作為參考去理解笼痹。

5配喳、Netty EventLoop

????????理解完JavaScript的EventLoop機(jī)制之后我們?cè)倩剡^(guò)頭來(lái)看看Netty EventLoop機(jī)制的具體實(shí)現(xiàn)。對(duì)比JavaScript單線程模型圖凳干,我畫(huà)了一張Netty的單線程模型圖:

????????在Netty的EventLoop線程中晴裹,這個(gè)線程主要需要處理IO事件和其他兩種任務(wù),分別為定時(shí)任務(wù)和一般任務(wù)救赐。Netty提供可一個(gè)參數(shù)ioRatio用于用戶(hù)調(diào)整單線程對(duì)于IO處理時(shí)間和任務(wù)處理時(shí)間的分配的比率涧团。這樣根據(jù)實(shí)際應(yīng)用場(chǎng)景用戶(hù)可以對(duì)這個(gè)值進(jìn)行調(diào)整,默認(rèn)值是50经磅,也就是這個(gè)線程會(huì)將處理IO的時(shí)間和處理任務(wù)的時(shí)間控制為1:1泌绣。

final long ioStartTime = System.nanoTime();

processSelectedKeys();

//處理IO事件 final long ioTime = System.nanoTime() - ioStartTime;

//處理IO事件的時(shí)間

runAllTasks(ioTime * (100 - ioRatio) / ioRatio);

//計(jì)算用于處理任務(wù)的時(shí)間

????????這樣盡管一個(gè)EventLoop會(huì)關(guān)聯(lián)多個(gè)Channel,這些Channel在單個(gè)線程下并不會(huì)出現(xiàn)并發(fā)問(wèn)題预厌,同時(shí)對(duì)于異步任務(wù)的處理也一樣阿迈,Netty這樣設(shè)計(jì)即免去了并發(fā)問(wèn)題的煩惱,有減少了多線程上下文切換帶來(lái)的性能損耗轧叽,同時(shí)基于EventLoopGroup實(shí)現(xiàn)的有限的線程數(shù)能夠充分利用CPU處理能力苗沧。

6、關(guān)于IO密集型和CPU密集型的思考

????????Netty基于單線程設(shè)計(jì)的EventLoop能夠同時(shí)處理成千上萬(wàn)的客戶(hù)端連接的IO事件炭晒,缺點(diǎn)是單線程不能夠處理時(shí)間過(guò)長(zhǎng)的任務(wù)待逞,這樣會(huì)阻塞使得IO事件的處理被阻塞,嚴(yán)重的時(shí)候回造成IO事件堆積网严,服務(wù)不能夠高效響應(yīng)客戶(hù)端請(qǐng)求识樱。所謂時(shí)間過(guò)長(zhǎng)的任務(wù)通常是占用CPU資源比較長(zhǎng)的任務(wù),也即CPU密集型,對(duì)于業(yè)務(wù)應(yīng)用也可能是業(yè)務(wù)代碼的耗時(shí)怜庸。這點(diǎn)和Node是極其相似的当犯,我可以認(rèn)為這是基于單線程的EventLoop模型的通病,我們不能夠?qū)⑦^(guò)長(zhǎng)的任務(wù)交給這個(gè)單線程來(lái)處理休雌,也就是不適合CPU密集型應(yīng)用灶壶。那么問(wèn)題怎么解決呢,參照Node的解決方案杈曲,當(dāng)我們遇到需要處理時(shí)間很長(zhǎng)的任務(wù)的時(shí)候驰凛,我們可以將它交給子線程來(lái)處理,主線程繼續(xù)去EventLoop担扑,當(dāng)子線程計(jì)算完畢再講結(jié)果交給主線程恰响。這也是通常基于Netty的應(yīng)用的解決方案涌献,通常業(yè)務(wù)代碼執(zhí)行時(shí)間比較長(zhǎng)胚宦,我們不能夠把業(yè)務(wù)邏輯交給這個(gè)單線程來(lái)處理,因此我們需要額外的線程池來(lái)分配線程資源來(lái)專(zhuān)門(mén)處理耗時(shí)較長(zhǎng)的業(yè)務(wù)邏輯燕垃,這是比較通用的設(shè)計(jì)方案枢劝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市卜壕,隨后出現(xiàn)的幾起案子您旁,更是在濱河造成了極大的恐慌,老刑警劉巖轴捎,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹤盒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡侦副,警方通過(guò)查閱死者的電腦和手機(jī)侦锯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秦驯,“玉大人尺碰,你說(shuō)我怎么就攤上這事∫氚” “怎么了亲桥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)细燎。 經(jīng)常有香客問(wèn)我,道長(zhǎng)皂甘,這世上最難降的妖魔是什么玻驻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上璧瞬,老公的妹妹穿的比我還像新娘户辫。我一直安慰自己,他們只是感情好嗤锉,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布渔欢。 她就那樣靜靜地躺著,像睡著了一般瘟忱。 火紅的嫁衣襯著肌膚如雪奥额。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,246評(píng)論 1 308
  • 那天访诱,我揣著相機(jī)與錄音垫挨,去河邊找鬼。 笑死触菜,一個(gè)胖子當(dāng)著我的面吹牛九榔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涡相,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼哲泊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了催蝗?” 一聲冷哼從身側(cè)響起切威,我...
    開(kāi)封第一講書(shū)人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎生逸,沒(méi)想到半個(gè)月后牢屋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡槽袄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年烙无,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遍尺。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡截酷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乾戏,到底是詐尸還是另有隱情迂苛,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布鼓择,位于F島的核電站三幻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏呐能。R本人自食惡果不足惜念搬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一抑堡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朗徊,春花似錦首妖、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至温亲,卻和暖如春棚壁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铸豁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工灌曙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人节芥。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓在刺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親头镊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚣驼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

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

  • 作者: 一字馬胡[http://www.reibang.com/u/86c421886c32] 轉(zhuǎn)載標(biāo)志 【2...
    一字馬胡閱讀 29,204評(píng)論 9 79
  • Netty是一款高效的NIO框架和工具,基于Java NIO實(shí)現(xiàn)相艇,Java NIO的Selector給Reacto...
    廿陸小生閱讀 4,828評(píng)論 1 4
  • BIO 1.BIO即阻塞式IO颖杏,使用BIO模型,一般會(huì)為每個(gè)Socket分配一個(gè)獨(dú)立的線程 為了避免頻繁創(chuàng)建和銷(xiāo)毀...
    Java_蘇先生閱讀 543評(píng)論 0 13
  • Reactor模型 Netty中的Reactor模型主要由多路復(fù)用器(Acceptor)坛芽、事件分發(fā)器(Dispat...
    jijs閱讀 3,335評(píng)論 0 14
  • 生命斷崖時(shí) 你還敢不要時(shí)間嗎留储? 生命的信仰在秒鐘里旋轉(zhuǎn) 信仰從容的面對(duì)"重生" "...
    水文君閱讀 115評(píng)論 1 0