前言
本篇文章主要分析Netty的系統(tǒng)結(jié)構(gòu)以及其如何實(shí)現(xiàn)其對外宣稱的特色呜师,如果還未了解Netty的基礎(chǔ)知識职祷,最好先閱讀本系列的第一篇文章Netty剖析 - 1. 基礎(chǔ)
Netty總體結(jié)構(gòu)
這張圖摘自Netty官網(wǎng)褥赊,其展示的是Netty的模塊結(jié)構(gòu)看幼,總體來說劈榨,Netty分為兩大模塊:
- 核心模塊
核心模塊主要提供的是Netty的一些基礎(chǔ)類和底層接口浙炼,主要包含三部分:- 用以提升性能吠谢,減少資源占用的Zero-Copy-Capable Rich Byte Buffer土童,即「零拷貝」緩沖區(qū),Netty里的「零拷貝」與操作系統(tǒng)語境下的「零拷貝」不是同一個概念工坊,具體會在后續(xù)章節(jié)做闡述
- 統(tǒng)一的API献汗,這是Netty對外宣傳的簡單易用API的一部分,什么意思呢栅组?就是Netty為同步和異步IO提供統(tǒng)一的編程接口雀瓢,舉個例子,如果在前期希望使用BIO玉掸,后續(xù)隨著業(yè)務(wù)變動刃麸,希望改用NIO,只需要改動幾個簡單的初始化參數(shù)司浪,而不需要變動主體流程泊业;相反,如果一開始不是基于Netty啊易,而是直接基于BIO書寫處理流程吁伺,后期想改成NIO,其變動是很大的租谈,畢竟是兩個不同的接口模塊
- 易擴(kuò)展的事件模型篮奄,這里的重點(diǎn)在于易擴(kuò)展,因?yàn)镹IO本身就是基于事件的IO模型割去,而擴(kuò)展性很好理解窟却,如果一個框架無法擴(kuò)展,那么也就意味著無法應(yīng)對業(yè)務(wù)的變化
- 服務(wù)模塊
既然Netty的核心是IO呻逆,那么其服務(wù)模塊基本也就和IO操作分不開了夸赫,主要有:- 網(wǎng)絡(luò)接口數(shù)據(jù)處理相關(guān)服務(wù),如報(bào)文的粘包咖城,拆包處理茬腿,數(shù)據(jù)的加密呼奢,解密等
- 各網(wǎng)絡(luò)層協(xié)議實(shí)現(xiàn)服務(wù),主要包括傳輸層和應(yīng)用層相關(guān)網(wǎng)絡(luò)協(xié)議的實(shí)現(xiàn)
- 文件處理相關(guān)服務(wù)
Netty處理架構(gòu)
介紹完Netty的模塊結(jié)構(gòu)切平,我們再來看一下它的處理架構(gòu):
Netty的架構(gòu)也很清晰握础,就三層:
- 底層IO復(fù)用層,負(fù)責(zé)實(shí)現(xiàn)多路復(fù)用
- 通用數(shù)據(jù)處理層揭绑,主要對傳輸層的數(shù)據(jù)在進(jìn)和出兩個方向進(jìn)行攔截處理弓候,如編/解碼,粘包處理等
- 應(yīng)用實(shí)現(xiàn)層他匪,開發(fā)者在使用Netty的時候基本就在這一層上折騰,同時Netty本身已經(jīng)在這一層提供了一些常用的實(shí)現(xiàn)夸研,如HTTP協(xié)議邦蜜,F(xiàn)TP協(xié)議等
一般來說,數(shù)據(jù)從網(wǎng)絡(luò)傳遞給IO復(fù)用層亥至,IO復(fù)用層收到數(shù)據(jù)后會將數(shù)據(jù)傳遞給上層進(jìn)行處理悼沈,這一層會通過一系列的處理Handler以及應(yīng)用服務(wù)對數(shù)據(jù)進(jìn)行處理,然后返回給IO復(fù)用層姐扮,通過它再傳回網(wǎng)絡(luò)
基于Reactor模式的IO復(fù)用
在Netty處理架構(gòu)圖中絮供,可以看到在IO復(fù)用層上標(biāo)注了一個「Reactor」:
這個「Reactor」代表的就是其IO復(fù)用層具體的實(shí)現(xiàn)模式 -- Reactor模式
這張圖是從大名鼎鼎的Doug Lea的一份演講稿中截取下來的,通過這張圖示茶敏,就可以大致明白什么是Reactor模式了壤靶。在Reactor模式中,分為主反應(yīng)組(MainReactor)和子反應(yīng)組(subReactor)以及ThreadPool惊搏,主反應(yīng)組(MainReactor)負(fù)責(zé)處理連接贮乳,連接建立完成以后由主線程對應(yīng)的acceptor將后續(xù)的數(shù)據(jù)處理(read/write)分發(fā)給子反應(yīng)組(subReactor)進(jìn)行處理,而Threadpool對應(yīng)的是業(yè)務(wù)處理線程池恬惯;對應(yīng)代碼為:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(1);
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
...
在這段代碼中bossGroup對應(yīng)的就是主反應(yīng)組(MainReactor)向拆,workerGroup對應(yīng)的是子反應(yīng)組(subReactor),而NioEventLoopGroup
其實(shí)就是一個實(shí)現(xiàn)了Java ExecutorService
的線程池酪耳,其中的線程數(shù)可定制浓恳,若不設(shè)置線程數(shù)參數(shù),則該參數(shù)值默認(rèn)為2 * CPU核數(shù)量
碗暗,在ServerBootstrap
的初始化過程中颈将,會為其添加一個實(shí)現(xiàn)了acceptor
機(jī)制的Handler
而通過
ServerBootstrapAcceptor
,會在Channel建立后觸發(fā)channelRead()
方法讹堤,并在channelRead()
內(nèi)將此Channel綁定至子反應(yīng)組對應(yīng)的處理線程吆鹤,后續(xù)的數(shù)據(jù)處理就交于它進(jìn)行處理在閱讀這部分源碼的時候需要注意一個點(diǎn),按理來說洲守,連接的建立應(yīng)該是
ACCEPT
事件疑务,怎么會觸發(fā)channelRead()
呢 沾凄?其實(shí)netty內(nèi)部將READ
和ACCEPT
狀態(tài)一并作為read的觸發(fā)條件介紹完了Netty關(guān)于IO復(fù)用層的實(shí)現(xiàn)知残,繼續(xù)看其「易擴(kuò)展」和「關(guān)注點(diǎn)分離」的核心:Pipeline
基于責(zé)任鏈模式的Channel-Pipeline
同樣回過頭去再看Netty處理架構(gòu)圖中的中間層 -- Pipeline
其字面意思為「管道」屡穗,顧名思義,管道的作用就在于傳輸许溅,而對于Netty來說温鸽,它的管道傳輸?shù)漠?dāng)然就是數(shù)據(jù)了保屯,如果閱讀過上一篇的讀者應(yīng)該知道,在上一篇關(guān)于Netty基礎(chǔ)的介紹里涤垫,提到它有一個很重要的特色就在于:基于事件機(jī)制(Pipeline - Handler)達(dá)成關(guān)注點(diǎn)分離(消息編解碼姑尺,協(xié)議編解碼,業(yè)務(wù)處理)蝠猬,而Pipeline
就是實(shí)現(xiàn)這一特色的核心所在切蟋,我們下面來看Netty是如何實(shí)現(xiàn)所謂的「易擴(kuò)展」和「關(guān)注點(diǎn)分離」的
首先,Netty的Pipeline
從數(shù)據(jù)傳輸?shù)姆较蛏蟻砜捶譃檫M(jìn)和出榆芦,這個和BIO相同柄粹;其次,最重要的在于Netty在Pipeline
上通過責(zé)任鏈模式插入一系列的「Handler」匆绣,這一結(jié)構(gòu)是它能實(shí)現(xiàn)「易擴(kuò)展」和「關(guān)注點(diǎn)分離」的關(guān)鍵驻右。想想看,所謂IO崎淳,不就是數(shù)據(jù)的「進(jìn)」和「出」嗎堪夭?而進(jìn)來干啥呢?當(dāng)然就是需要應(yīng)用邏輯對其處理凯力,那處理完了呢茵瘾?還需要送回給請求方以示響應(yīng),而在進(jìn)的過程中需要哪些處理邏輯咐鹤,這些處理邏輯的先后順序如何拗秘,處理完后出去的過程中需要哪些處理邏輯,這些處理邏輯的順序又是如何祈惶,如果這些都可以方便的配置調(diào)整雕旨,是不是就達(dá)到了Netty宣稱的「易擴(kuò)展」和「關(guān)注點(diǎn)分離」(只需關(guān)注業(yè)務(wù)相關(guān)的Handler,網(wǎng)絡(luò)協(xié)議相關(guān)的Handler直接調(diào)用即可捧请,IO復(fù)用更無須關(guān)注)呢凡涩?
在Netty里,這一實(shí)現(xiàn)機(jī)制的核心類叫做ChannelPipeline
:
其中
Channel
負(fù)責(zé)數(shù)據(jù)通信疹蛉,Handler
負(fù)責(zé)邏輯處理活箕,而ChannelPipeline
就相當(dāng)于一個由Handler
串起來的處理鏈條,在Neety源碼里有一個關(guān)于ChannelPipeline
的比較形象的圖形化描述:看到?jīng)]有可款,其實(shí)很簡單育韩,就是一個針對不同方向數(shù)據(jù)流的責(zé)任鏈克蚂,其中Inbound
對應(yīng)的是輸入流,Outbound
對應(yīng)的是輸出流(在這里再多提一句筋讨,責(zé)任鏈模式在很多框架里都有使用埃叭,比如Spring MVC里看到的各種Handler,也是基于責(zé)任鏈的封裝)
強(qiáng)大的ByteBuf
既然是對Netty進(jìn)行分析悉罕,就必然繞不過Netty自己封裝的數(shù)據(jù)緩沖區(qū):ByteBuf赤屋,它是Netty對外宣稱的高性能的重要支撐,另外有必要提一下壁袄,在Netty里其核心緩沖區(qū)類叫「ByteBuf」类早,以便與NIO本身的緩沖區(qū)類「ByteBuffer」做區(qū)分,ByteBuf有如下特點(diǎn):
- 功能豐富的接口嗜逻,Java NIO本身的緩沖區(qū)接口比較簡單
- 支持零拷貝莺奔,提升性能,減少資源占用
- 支持動態(tài)擴(kuò)展
- 緩沖區(qū)初始塊大小動態(tài)控制
- 讀寫切換不需要手動調(diào)用clear()变泄,flip();使用過Java NIO的小伙伴應(yīng)該知道恼琼,其在進(jìn)行讀寫切換時需要不停的通過clear()和flip()進(jìn)行模式切換妨蛹,很麻煩
- 池化,提升性能晴竞,減少資源占用
下面將對上面提到的幾個ByteBuf的重要特性進(jìn)行實(shí)現(xiàn)分析蛙卤,首先來看下ByteBuf是如何避免NIO中那繁瑣的讀寫切換的。我們知道噩死,對于Java NIO的Buffer颤难,其有幾個重要的屬性:position
,limit
已维,capacity
行嗤,其中position
代表的是下一個讀或?qū)懙奈恢茫?code>limit是可被讀或?qū)懙淖罡呶唬?code>capacity就是Buffer的容量了垛耳,之所以要在讀和寫切換的時候進(jìn)行手動操作(clear()
栅屏,flip()
),主要是因?yàn)樵贜IO中堂鲜,position
和limit
在讀的時候代表的是下一個需讀的位和可讀的最高位栈雳,但是在寫的時候又代表下一個需寫的位和可寫的最高位(其實(shí)就是capacity
),換句話說這兩個變量在不同的操作場景下有不同的含義缔莲,對應(yīng)值也不同哥纫,所以需要在讀寫切換的時候進(jìn)行手動操作
而Netty的ByteBuf則對這一點(diǎn)做了改進(jìn),其針對讀寫操作分別增加上了
readerIndex
痴奏,writerIndex
蛀骇,使用的時候不需要考慮讀寫轉(zhuǎn)換讀的時候就變動
readerIndex
的值厌秒,而此時可讀的最高位(對應(yīng)NIO中的limit)其實(shí)就是writerIndex
,同理寫的時候就變動writerIndex
松靡,此時可寫的最高位(對應(yīng)NIO中的limit)就是capacity
简僧,說白了就是兩個變量分別管理讀和寫的操作位,互不沖突雕欺,也就不存在讀寫切換的時候手動操作了岛马;其實(shí)看到這里我們可以發(fā)現(xiàn),NIO在接口設(shè)計(jì)的時候確實(shí)沒有考慮周到屠列,畢竟Netty的這種優(yōu)化并不是有多難啦逆!
零拷貝Buf
在分析ByteBuf的「零拷貝」特性之前,先說說什么是「零拷貝」笛洛,所謂「零拷貝」, 通常指的是在 OS 層面上為了避免在用戶態(tài)(User-space) 與 內(nèi)核態(tài)(Kernel-space) 之間進(jìn)行數(shù)據(jù)拷貝而采取的性能優(yōu)化措施夏志;例如 Linux 提供的 mmap 系統(tǒng)調(diào)用,它可以將一段用戶空間內(nèi)存映射到內(nèi)核空間苛让,當(dāng)映射成功后沟蔑,用戶對這段內(nèi)存區(qū)域的修改可以直接反映到內(nèi)核空間;同樣地狱杰,內(nèi)核空間對這段區(qū)域的修改也直接反映用戶空間瘦材。正因?yàn)橛羞@樣的映射關(guān)系,我們就不需要在用戶態(tài)(User-space) 與內(nèi)核態(tài)(Kernel-space) 之間拷貝數(shù)據(jù)仿畸,從而提高了數(shù)據(jù)傳輸?shù)男适匙兀粚τ贘ava的網(wǎng)絡(luò)操作來說,網(wǎng)絡(luò)接口在收到數(shù)據(jù)的時候需要先將數(shù)據(jù)復(fù)制到內(nèi)核內(nèi)存错沽,然后在從內(nèi)核內(nèi)存復(fù)制到用戶內(nèi)存簿晓,同理往網(wǎng)絡(luò)接口發(fā)數(shù)據(jù)也是先將數(shù)據(jù)從用戶內(nèi)存復(fù)制到內(nèi)核內(nèi)存,再從內(nèi)核內(nèi)存中將數(shù)據(jù)傳給網(wǎng)絡(luò)接口千埃,所以如果是直接操縱內(nèi)核內(nèi)存憔儿,無疑處理的性能會更好
回到Netty,Netty中的 「零拷貝」與上面我們所提到到 OS 層面上的 「零拷貝」其實(shí)不太一樣镰禾,Netty的 「零拷貝」 完全是在用戶態(tài)里的皿曲,或者說更多的是偏向于減少JVM內(nèi)的數(shù)據(jù)操作,具體體現(xiàn)在如下幾個方面:
- 通過
CompositeByteBuf
類吴侦,將多個ByteBuf
合并為一個邏輯上的ByteBuf
屋休,避免了各個ByteBuf
之間的拷貝 - 通過
wrap
操作,將byte[]
數(shù)組备韧、ByteBuf
劫樟、ByteBuffer
等多個數(shù)據(jù)容器合并成一個ByteBuf
對象,進(jìn)而避免了拷貝操作 - 通過
slice
操作,將ByteBuf
分解為多個共享同一個存儲區(qū)域的ByteBuf
叠艳,避免了內(nèi)存的拷貝 - 通過
FileRegion
包裝的FileChannel.tranferTo
實(shí)現(xiàn)文件傳輸奶陈,將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo)Channel
,避免了傳統(tǒng)通過循環(huán)write
方式導(dǎo)致的內(nèi)存拷貝問題
這些操作之所以能避免不必要的拷貝操作附较,其實(shí)就在于內(nèi)部對數(shù)據(jù)進(jìn)行的是邏輯操作而非物理操作吃粒,操作完成后根據(jù)各邏輯引用的數(shù)據(jù)信息(大小,位置等)重新計(jì)算ByteBuf
內(nèi)部的控制屬性(limit
拒课,capacity
徐勃,readerIndex
,writerIndex
)早像,如通過CompositeByteBuf
將原本兩個分別表示head和body的buffer組裝成一個buffer:
雖然看起來
CompositeByteBuf
是由兩個ByteBuf
組合而成的僻肖,不過在CompositeByteBuf
內(nèi)部,這兩個ByteBuf
都是單獨(dú)存在的(指針引用)卢鹦,CompositeByteBuf
只是邏輯上是一個整體臀脏;這樣在數(shù)據(jù)操作的時候不需要對數(shù)據(jù)進(jìn)行物理挪動,只需要操作數(shù)據(jù)引用并計(jì)算關(guān)鍵因子即可冀自,這種方式不但能提升性能揉稚,還可以減少內(nèi)存占用,值得借鑒
Buf池化
在Netty中熬粗,ByteBuf用來作為數(shù)據(jù)的容器窃植,是一種會被頻繁創(chuàng)建和銷毀的對象,ByteBuf需要的內(nèi)存空間荐糜,可以在 JVM Heap 中申請分配,也可以在Direct Memory(堆外內(nèi)存)中申請葛超,其中在 Direct Memory 中分配的ByteBuf暴氏,其創(chuàng)建和銷毀的代價比在 JVM Heap 中的更高,但拋開哪個代價高哪個代價低不說绣张,光是頻繁創(chuàng)建和頻繁銷毀這一點(diǎn)答渔,就已奠定了效率不高的基調(diào)。Netty為了解決這個問題侥涵,引入了池化技術(shù)沼撕,池化技術(shù)的思想不復(fù)雜,和線程池思想類似芜飘,說白了就是對一些可重用的對象用完不回收务豺,后面需要再次使用,以減少創(chuàng)建和銷毀對象帶來的資源損耗嗦明,下面結(jié)合Netty源碼對其池化技術(shù)做剖析
首先看ByteBuf
,它實(shí)現(xiàn)了ReferenceCounted
接口,表明該類是一個引用計(jì)數(shù)管理對象
public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf>
而引用計(jì)數(shù)就是實(shí)現(xiàn)池化的關(guān)鍵技術(shù)點(diǎn)(不過并非只有池化的 ByteBuf 才有引用計(jì)數(shù)奔浅,非池化的也會有引用)馆纳,繼續(xù)看ReferenceCounted
接口,它定義了這幾個方法:
public interface ReferenceCounted {
int refCnt();
ReferenceCounted retain();
ReferenceCounted retain(int increment);
boolean release();
boolean release(int decrement);
}
每一個引用計(jì)數(shù)對象汹桦,都維護(hù)了一個自身的引用計(jì)數(shù)鲁驶,當(dāng)?shù)谝淮伪粍?chuàng)建時,引用計(jì)數(shù)為1舞骆,通過refCnt()
方法可以得到當(dāng)前的引用計(jì)數(shù)钥弯,retain()
和retain(int increment)
增加自身的引用計(jì)數(shù)值,而release()
和release(int increment)
則減少當(dāng)前的引用計(jì)數(shù)值葛作,如果引用計(jì)數(shù)值為 0寿羞,并且當(dāng)前的 ByteBuf 被釋放成功,那這兩個方法的返回值就為true
赂蠢。而具體如何釋放绪穆,各種不同類型的ByteBuf自己決定,如果是池化的ByteBuf虱岂,那么就會重新進(jìn)池子玖院,以待重用;如果是非池化的第岖,則銷毀底層的字節(jié)數(shù)組引用或者釋放對應(yīng)的堆外內(nèi)存难菌。具體的邏輯在AbstractReferenceCountedByteBuf
類中可以看到:
@Override
public final boolean release() {
for (;;) {
int refCnt = this.refCnt;
if (refCnt == 0) {
throw new IllegalReferenceCountException(0, -1);
}
if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) {
if (refCnt == 1) {
deallocate();
return true;
}
return false;
}
}
}
釋放對象的方法定義在 deallocate() 方法里,它是個抽象方法蔑滓,既然是抽象的郊酒,那么就需要子類自行實(shí)現(xiàn),對于非池化的 HeapByteBuf
來說键袱,釋放對象實(shí)際上就是釋放底層字節(jié)數(shù)組的引用:
@Override
protected void deallocate() {
array = null;
}
對于非池化的DirectByteBuf
來說燎窘,釋放對象實(shí)際上就是釋放堆外內(nèi)存:
@Override
protected void deallocate() {
ByteBuffer buffer = this.buffer;
if (buffer == null) {
return;
}
this.buffer = null;
if (!doNotFree) {
PlatformDependent.freeDirectBuffer(buffer);
}
if (leak != null) {
leak.close();
}
}
對于池化的 ByteBuf 來說,就是把自己歸還到對象池里:
@Override
protected final void deallocate() {
if (handle >= 0) {
final long handle = this.handle;
this.handle = -1;
memory = null;
chunk.arena.free(chunk, handle);
if (leak != null) {
leak.close();
} else {
recycle();
}
}
}
熟悉JVM GC的同學(xué)應(yīng)該對這個引用計(jì)數(shù)的機(jī)制不會感到陌生蹄咖,因?yàn)镴VM在判斷一個Java對象是否存活時有一種方式使用的就是計(jì)數(shù)法褐健;另外Netty的池化緩存在實(shí)現(xiàn)上借鑒了buddy allocation和slab allocation的思想并進(jìn)行了比較復(fù)雜的設(shè)計(jì)(buddy allocation是基于一定規(guī)則對內(nèi)存進(jìn)行分割,回收時進(jìn)行合并澜汤,盡可能保證系統(tǒng)有足夠的連續(xù)內(nèi)存蚜迅;而slab allocation是把內(nèi)存分割為大小不等的內(nèi)存塊,請求內(nèi)存是分配最貼近請求size的內(nèi)存塊俊抵,避免內(nèi)存浪費(fèi))谁不,可以減少對象的創(chuàng)建與銷毀對性能的影響,因?yàn)榫彌_區(qū)對象的創(chuàng)建與銷毀會占用內(nèi)存帶寬以及GC資源徽诲,另外由于池化緩存本身比較復(fù)雜拍谐,如線程私有池與全局共有池烛缔,其聲明與釋放都需要手動處理(比如本地池內(nèi)的緩沖區(qū)對象如果不是在同一個線程內(nèi)釋放就會導(dǎo)致內(nèi)存泄漏,這也是為什么JVM GC的時候需要有Stop The World)轩拨,Netty提供了內(nèi)存泄漏監(jiān)控工具ResourceLeakDetector践瓷,如果發(fā)生了內(nèi)存泄漏,它會通過日志記錄并提醒亡蓉,這個工具主要是防止對象被GC的時候其占用的資源沒有被釋放(如內(nèi)存)晕翠,或者沒有執(zhí)行release
方法
也許有人會說,既然池化緩存實(shí)現(xiàn)復(fù)雜砍濒,用起來還得防止內(nèi)存泄漏淋肾,那么它到底能給性能帶來多大提升呢?我們可以看下Twitter對Netty池化緩存做的性能測試結(jié)果:
這張圖的Y軸顯示的是創(chuàng)建對象花費(fèi)的時間爸邢,而X軸代表的是所創(chuàng)建對象的大小樊卓,同時在實(shí)驗(yàn)中,使用了四種不同的對象杠河,分別是非池化堆內(nèi)存對象(Unpooled Heap)碌尔,池化堆內(nèi)存對象(Pooled Heap),非池化直接內(nèi)存對象(Unpooled Direct)券敌,池化直接內(nèi)存對象(Pooled Direct)唾戚。結(jié)果現(xiàn)實(shí),隨著被創(chuàng)建對象大小的增加待诅,池化技術(shù)的優(yōu)勢愈加明顯叹坦,當(dāng)然當(dāng)對象很小時,池化反而不如JVM本身的對象創(chuàng)建性能(可以結(jié)合ByteBuf的實(shí)現(xiàn)原理卑雁,想想為什么募书?)
除了對象創(chuàng)建的性能,Twitter還測試了使用池化技術(shù)時GC相關(guān)的表現(xiàn)测蹲,實(shí)驗(yàn)?zāi)M了在16000個連接下锐膜,對256byte大小的數(shù)據(jù)包進(jìn)行循環(huán)傳輸:
結(jié)果表明,相對于非池化弛房,池化的GC停頓減少了近4倍,而垃圾的增長也慢了4倍而柑。所以說文捶,Netty對ByteBuf進(jìn)行的復(fù)雜的重寫還是值得的
NIO epoll死循環(huán)問題及Netty解決方案
最后說說Netty是如何解決著名的「NIO epoll死循環(huán)」問題的。什么是「NIO epoll死循環(huán)」呢媒咳?在Linux系統(tǒng)中粹排,當(dāng)某個socket的連接突然中斷后,會重設(shè)事件集eventSet涩澡,而eventSet的重設(shè)就會導(dǎo)致Selector被喚醒(但其實(shí)這個時候是沒有任何事件需要處理的顽耳,select()
方法應(yīng)該還是處于阻塞狀態(tài)),雖然被喚醒了,但其實(shí)是沒有事件需要處理的射富,所以就又返回select()
方法之前(正常情況下是處理完事件重新回去被select()
阻塞)膝迎,此時select()
方法還是會直接返回,如此反復(fù)便造成死循環(huán):
這個問題的原因本質(zhì)上就是NIO的Selector實(shí)現(xiàn)有問題胰耗,Netty解決的方式其實(shí)比較簡單粗暴限次,它會記錄一段時間內(nèi)空輪詢的次數(shù),如果超過一定閾值柴灯,就認(rèn)為這個bug出現(xiàn)了卖漫,此時會重新生成一個新的selector取代舊的selector,避免死循環(huán)赠群,具體的處理代碼在
NioEventLoop
中:Netty主要類關(guān)系圖
這里貼一張Netty主要實(shí)現(xiàn)類的關(guān)系圖羊始,對需要閱讀Netty源碼的小伙伴可能有一個參考作用
總結(jié)
本篇主要介紹了Netty的總體架構(gòu),并對Netty的一些重要的實(shí)現(xiàn)機(jī)制進(jìn)行了簡單的剖析查描,如果在閱讀本篇時發(fā)現(xiàn)對一些基礎(chǔ)的概念和知識不是很了解突委,可以閱讀本系列的第一篇Netty剖析 - 1. 基礎(chǔ)進(jìn)行相關(guān)學(xué)習(xí),如果希望對Netty的實(shí)現(xiàn)有更深入的了解叹誉,推薦去Netty的官網(wǎng)鸯两,或者閱讀Netty源碼,如果還希望了解Netty其他相關(guān)知識长豁,也可以閱讀本系列的最后一篇文章Netty剖析 - 3. 總結(jié)