日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Netty系列之Netty可靠性分析

 集微筆記 2014-06-24

1. 背景

1.1. 宕機的代價

1.1.1. 電信行業(yè)

畢馬威國際(KPMG International)在對46個國家的74家運營商進行調(diào)查后發(fā)現(xiàn),全球通信行業(yè)每年的收益流失約為400億美元,占總收入的1%-3%。導(dǎo)致收益流失的因素有多種,主要原因就是計費BUG。

1.1.2. 互聯(lián)網(wǎng)行業(yè)

美國太平洋時間8月16日下午3點50分到3點55分(北京時間8月17日6點50分到6點55分),谷歌遭遇了宕機。根據(jù)事后統(tǒng)計,短短的5分鐘,谷歌損失了54.5萬美元。也就是服務(wù)每中斷一分鐘,損失就達10.8萬美元。

2013年,從美國東部時間8月19日下午2點45分開始,有用戶率先發(fā)現(xiàn)了亞馬遜網(wǎng)站出現(xiàn)宕機,大約在20多分鐘后又恢復(fù)正常。此次宕機讓亞馬遜每分鐘損失近6.7萬美元,在宕機期間,消費者無法通過Amazon.com、亞馬遜移動端以及Amazon.ca等網(wǎng)站進行購物。

1.2. 軟件可靠性

軟件可靠性是指在給定時間內(nèi),特定環(huán)境下軟件無錯運行的概率。軟件可靠性包含了以下三個要素:

1) 規(guī)定的時間:軟件可靠性只是體現(xiàn)在其運行階段,所以將運行時間作為規(guī)定的時間的度量。運行時間包括軟件系統(tǒng)運行后工作與掛起(開啟但空閑)的累計時間。由于軟件運行的環(huán)境與程序路徑選取的隨機性,軟件的失效為隨機事件,所以運行時間屬于隨機變量;

2) 規(guī)定的環(huán)境條件:環(huán)境條件指軟件的運行環(huán)境。它涉及軟件系統(tǒng)運行時所需的各種支持要素,如支持硬件、操作系統(tǒng)、其它支持軟件、輸入數(shù)據(jù)格式和范圍以及操作規(guī)程等。不同的環(huán)境條件下軟件的可靠性是不同的。具體地說,規(guī)定的環(huán)境條件主要是描述軟件系統(tǒng)運行時計算機的配置情況以及對輸入數(shù)據(jù)的要求,并假定其它一切因素都是理想的。有了明確規(guī)定的環(huán)境條件,還可以有效判斷軟件失效的責(zé)任在用戶方還是提供方;

3) 規(guī)定的功能:軟件可靠性還與規(guī)定的任務(wù)和功能有關(guān)。由于要完成的任務(wù)不同,軟件的運行剖面會有所區(qū)別,則調(diào)用的子模塊就不同(即程序路徑選擇不同),其可靠性也就可能不同。所以要準(zhǔn)確度量軟件系統(tǒng)的可靠性必須首先明確它的任務(wù)和功能。

1.3. Netty的可靠性

首先,我們要從Netty的主要用途來分析它的可靠性,Netty目前的主流用法有三種:

1) 構(gòu)建RPC調(diào)用的基礎(chǔ)通信組件,提供跨節(jié)點的遠程服務(wù)調(diào)用能力;

2) NIO通信框架,用于跨節(jié)點的數(shù)據(jù)交換;

3) 其它應(yīng)用協(xié)議棧的基礎(chǔ)通信組件,例如HTTP協(xié)議以及其它基于Netty開發(fā)的應(yīng)用層協(xié)議棧。

以阿里的分布式服務(wù)框架Dubbo為例,Netty是Dubbo RPC框架的核心。它的服務(wù)調(diào)用示例圖如下:

圖1-1 Dubbo的節(jié)點角色說明圖

其中,服務(wù)提供者和服務(wù)調(diào)用者之間可以通過Dubbo協(xié)議進行RPC調(diào)用,消息的收發(fā)默認(rèn)通過Netty完成。

通過對Netty主流應(yīng)用場景的分析,我們發(fā)現(xiàn)Netty面臨的可靠性問題大致分為三類:

1) 傳統(tǒng)的網(wǎng)絡(luò)I/O故障,例如網(wǎng)絡(luò)閃斷、防火墻Hang住連接、網(wǎng)絡(luò)超時等;

2) NIO特有的故障,例如NIO類庫特有的BUG、讀寫半包處理異常、Reactor線程跑飛等等;

3) 編解碼相關(guān)的異常。

在大多數(shù)的業(yè)務(wù)應(yīng)用場景中,一旦因為某些故障導(dǎo)致Netty不能正常工作,業(yè)務(wù)往往會陷入癱瘓。所以,從業(yè)務(wù)訴求來看,對Netty框架的可靠性要求是非常的高。作為當(dāng)前業(yè)界最流行的一款NIO框架,Netty在不同行業(yè)和領(lǐng)域都得到了廣泛的應(yīng)用,它的高可靠性已經(jīng)得到了成百上千的生產(chǎn)系統(tǒng)檢驗。

Netty是如何支持系統(tǒng)高可靠性的?下面,我們就從幾個不同維度出發(fā)一探究竟。

2. Netty高可靠性之道

2.1. 網(wǎng)絡(luò)通信類故障

2.1.1. 客戶端連接超時

在傳統(tǒng)的同步阻塞編程模式下,客戶端Socket發(fā)起網(wǎng)絡(luò)連接,往往需要指定連接超時時間,這樣做的目的主要有兩個:

1) 在同步阻塞I/O模型中,連接操作是同步阻塞的,如果不設(shè)置超時時間,客戶端I/O線程可能會被長時間阻塞,這會導(dǎo)致系統(tǒng)可用I/O線程數(shù)的減少;

2) 業(yè)務(wù)層需要:大多數(shù)系統(tǒng)都會對業(yè)務(wù)流程執(zhí)行時間有限制,例如WEB交互類的響應(yīng)時間要小于3S??蛻舳嗽O(shè)置連接超時時間是為了實現(xiàn)業(yè)務(wù)層的超時。

JDK原生的Socket連接接口定義如下:

圖2-1 JDK Socket連接超時接口

對于NIO的SocketChannel,在非阻塞模式下,它會直接返回連接結(jié)果,如果沒有連接成功,也沒有發(fā)生IO異常,則需要將SocketChannel注冊到Selector上監(jiān)聽連接結(jié)果。所以,異步連接的超時無法在API層面直接設(shè)置,而是需要通過定時器來主動監(jiān)測。

下面我們首先看下JDK NIO類庫的SocketChannel連接接口定義:

圖2-2 JDK NIO 類庫SocketChannel連接接口

從上面的接口定義可以看出,NIO類庫并沒有現(xiàn)成的連接超時接口供用戶直接使用,如果要在NIO編程中支持連接超時,往往需要NIO框架或者用戶自己封裝實現(xiàn)。

下面我們看下Netty是如何支持連接超時的,首先,在創(chuàng)建NIO客戶端的時候,可以配置連接超時參數(shù):

圖2-3 Netty客戶端創(chuàng)建支持設(shè)置連接超時參數(shù)

設(shè)置完連接超時之后,Netty在發(fā)起連接的時候,會根據(jù)超時時間創(chuàng)建ScheduledFuture掛載在Reactor線程上,用于定時監(jiān)測是否發(fā)生連接超時,相關(guān)代碼如下:

圖2-4 根據(jù)連接超時創(chuàng)建超時監(jiān)測定時任務(wù)

創(chuàng)建連接超時定時任務(wù)之后,會由NioEventLoop負(fù)責(zé)執(zhí)行。如果已經(jīng)連接超時,但是服務(wù)端仍然沒有返回TCP握手應(yīng)答,則關(guān)閉連接,代碼如上圖所示。

如果在超時期限內(nèi)處理完成連接操作,則取消連接超時定時任務(wù),相關(guān)代碼如下:

圖2-5 取消連接超時定時任務(wù)

Netty的客戶端連接超時參數(shù)與其它常用的TCP參數(shù)一起配置,使用起來非常方便,上層用戶不用關(guān)心底層的超時實現(xiàn)機制。這既滿足了用戶的個性化需求,又實現(xiàn)了故障的分層隔離。

2.1.2. 通信對端強制關(guān)閉連接

在客戶端和服務(wù)端正常通信過程中,如果發(fā)生網(wǎng)絡(luò)閃斷、對方進程突然宕機或者其它非正常關(guān)閉鏈路事件時,TCP鏈路就會發(fā)生異常。由于TCP是全雙工的,通信雙方都需要關(guān)閉和釋放Socket句柄才不會發(fā)生句柄的泄漏。

在實際的NIO編程過程中,我們經(jīng)常會發(fā)現(xiàn)由于句柄沒有被及時關(guān)閉導(dǎo)致的功能和可靠性問題。究其原因總結(jié)如下:

1) IO的讀寫等操作并非僅僅集中在Reactor線程內(nèi)部,用戶上層的一些定制行為可能會導(dǎo)致IO操作的外逸,例如業(yè)務(wù)自定義心跳機制。這些定制行為加大了統(tǒng)一異常處理的難度,IO操作越發(fā)散,故障發(fā)生的概率就越大;

2) 一些異常分支沒有考慮到,由于外部環(huán)境誘因?qū)е鲁绦蜻M入這些分支,就會引起故障。

下面我們通過故障模擬,看Netty是如何處理對端鏈路強制關(guān)閉異常的。首先啟動Netty服務(wù)端和客戶端,TCP鏈路建立成功之后,雙方維持該鏈路,查看鏈路狀態(tài),結(jié)果如下:

圖2-6 Netty服務(wù)端和客戶端TCP鏈路狀態(tài)正常

強制關(guān)閉客戶端,模擬客戶端宕機,服務(wù)端控制臺打印如下異常:

圖2-7 模擬TCP鏈路故障

從堆棧信息可以判斷,服務(wù)端已經(jīng)監(jiān)控到客戶端強制關(guān)閉了連接,下面我們看下服務(wù)端是否已經(jīng)釋放了連接句柄,再次執(zhí)行netstat命令,執(zhí)行結(jié)果如下:

圖2-8 查看故障鏈路狀態(tài)

從執(zhí)行結(jié)果可以看出,服務(wù)端已經(jīng)關(guān)閉了和客戶端的TCP連接,句柄資源正常釋放。由此可以得出結(jié)論,Netty底層已經(jīng)自動對該故障進行了處理。

下面我們一起看下Netty是如何感知到鏈路關(guān)閉異常并進行正確處理的,查看AbstractByteBuf的writeBytes方法,它負(fù)責(zé)將指定Channel的緩沖區(qū)數(shù)據(jù)寫入到ByteBuf中,詳細代碼如下:

圖2-9 AbstractByteBuf的writeBytes方法

在調(diào)用SocketChannel的read方法時發(fā)生了IOException,代碼如下:

圖2-10 讀取緩沖區(qū)數(shù)據(jù)發(fā)生IO異常

為了保證IO異常被統(tǒng)一處理,該異常向上拋,由AbstractNioByteChannel進行統(tǒng)一異常處理,代碼如下:

圖2-11 鏈路異常退出異常處理

為了能夠?qū)Ξ惓2呗赃M行統(tǒng)一,也為了方便維護,防止處理不當(dāng)導(dǎo)致的句柄泄漏等問題,句柄的關(guān)閉,統(tǒng)一調(diào)用AbstractChannel的close方法,代碼如下:

圖2-12 統(tǒng)一的Socket句柄關(guān)閉接口

2.1.3. 正常的連接關(guān)閉

對于短連接協(xié)議,例如HTTP協(xié)議,通信雙方數(shù)據(jù)交互完成之后,通常按照雙方的約定由服務(wù)端關(guān)閉連接,客戶端獲得TCP連接關(guān)閉請求之后,關(guān)閉自身的Socket連接,雙方正式斷開連接。

在實際的NIO編程過程中,經(jīng)常存在一種誤區(qū):認(rèn)為只要是對方關(guān)閉連接,就會發(fā)生IO異常,捕獲IO異常之后再關(guān)閉連接即可。實際上,連接的合法關(guān)閉不會發(fā)生IO異常,它是一種正常場景,如果遺漏了該場景的判斷和處理就會導(dǎo)致連接句柄泄漏。

下面我們一起模擬故障,看Netty是如何處理的。測試場景設(shè)計如下:改造下Netty客戶端,雙發(fā)鏈路建立成功之后,等待120S,客戶端正常關(guān)閉鏈路??捶?wù)端是否能夠感知并釋放句柄資源。

首先啟動Netty客戶端和服務(wù)端,雙方TCP鏈路連接正常:

圖2-13 TCP連接狀態(tài)正常

120S之后,客戶端關(guān)閉連接,進程退出,為了能夠看到整個處理過程,我們在服務(wù)端的Reactor線程處設(shè)置斷點,先不做處理,此時鏈路狀態(tài)如下:

圖2-14 TCP連接句柄等待釋放

從上圖可以看出,此時服務(wù)端并沒有關(guān)閉Socket連接,鏈路處于CLOSE_WAIT狀態(tài),放開代碼讓服務(wù)端執(zhí)行完,結(jié)果如下:

圖2-15 TCP連接句柄正常釋放

下面我們一起看下服務(wù)端是如何判斷出客戶端關(guān)閉連接的,當(dāng)連接被對方合法關(guān)閉后,被關(guān)閉的SocketChannel會處于就緒狀態(tài),SocketChannel的read操作返回值為-1,說明連接已經(jīng)被關(guān)閉,代碼如下:

圖2-16 需要對讀取的字節(jié)數(shù)進行判斷

如果SocketChannel被設(shè)置為非阻塞,則它的read操作可能返回三個值:

1) 大于0,表示讀取到了字節(jié)數(shù);

2) 等于0,沒有讀取到消息,可能TCP處于Keep-Alive狀態(tài),接收到的是TCP握手消息;

3) -1,連接已經(jīng)被對方合法關(guān)閉。

通過調(diào)試,我們發(fā)現(xiàn),NIO類庫的返回值確實為-1:

圖2-17 鏈路正常關(guān)閉,返回值為-1

得知連接關(guān)閉之后,Netty將關(guān)閉操作位設(shè)置為true,關(guān)閉句柄,代碼如下:

圖2-18 連接正常關(guān)閉,釋放資源

2.1.4. 故障定制

在大多數(shù)場景下,當(dāng)?shù)讓泳W(wǎng)絡(luò)發(fā)生故障的時候,應(yīng)該由底層的NIO框架負(fù)責(zé)釋放資源,處理異常等。上層的業(yè)務(wù)應(yīng)用不需要關(guān)心底層的處理細節(jié)。但是,在一些特殊的場景下,用戶可能需要感知這些異常,并針對這些異常進行定制處理,例如:

1) 客戶端的斷連重連機制;

2) 消息的緩存重發(fā);

3) 接口日志中詳細記錄故障細節(jié);

4) 運維相關(guān)功能,例如告警、觸發(fā)郵件/短信等

Netty的處理策略是發(fā)生IO異常,底層的資源由它負(fù)責(zé)釋放,同時將異常堆棧信息以事件的形式通知給上層用戶,由用戶對異常進行定制。這種處理機制既保證了異常處理的安全性,也向上層提供了靈活的定制能力。

具體接口定義以及默認(rèn)實現(xiàn)如下:

圖2-19 故障定制接口

用戶可以覆蓋該接口,進行個性化的異常定制。例如發(fā)起重連等。

2.2. 鏈路的有效性檢測

當(dāng)網(wǎng)絡(luò)發(fā)生單通、連接被防火墻Hang住、長時間GC或者通信線程發(fā)生非預(yù)期異常時,會導(dǎo)致鏈路不可用且不易被及時發(fā)現(xiàn)。特別是異常發(fā)生在凌晨業(yè)務(wù)低谷期間,當(dāng)早晨業(yè)務(wù)高峰期到來時,由于鏈路不可用會導(dǎo)致瞬間的大批量業(yè)務(wù)失敗或者超時,這將對系統(tǒng)的可靠性產(chǎn)生重大的威脅。

從技術(shù)層面看,要解決鏈路的可靠性問題,必須周期性的對鏈路進行有效性檢測。目前最流行和通用的做法就是心跳檢測。

心跳檢測機制分為三個層面:

1) TCP層面的心跳檢測,即TCP的Keep-Alive機制,它的作用域是整個TCP協(xié)議棧;

2) 協(xié)議層的心跳檢測,主要存在于長連接協(xié)議中。例如SMPP協(xié)議;

3) 應(yīng)用層的心跳檢測,它主要由各業(yè)務(wù)產(chǎn)品通過約定方式定時給對方發(fā)送心跳消息實現(xiàn)。

心跳檢測的目的就是確認(rèn)當(dāng)前鏈路可用,對方活著并且能夠正常接收和發(fā)送消息。

做為高可靠的NIO框架,Netty也提供了心跳檢測機制,下面我們一起熟悉下心跳的檢測原理。

圖2-20 心跳檢測機制

不同的協(xié)議,心跳檢測機制也存在差異,歸納起來主要分為兩類:

1) Ping-Pong型心跳:由通信一方定時發(fā)送Ping消息,對方接收到Ping消息之后,立即返回Pong應(yīng)答消息給對方,屬于請求-響應(yīng)型心跳;

2) Ping-Ping型心跳:不區(qū)分心跳請求和應(yīng)答,由通信雙方按照約定定時向?qū)Ψ桨l(fā)送心跳Ping消息,它屬于雙向心跳。

心跳檢測策略如下:

1) 連續(xù)N次心跳檢測都沒有收到對方的Pong應(yīng)答消息或者Ping請求消息,則認(rèn)為鏈路已經(jīng)發(fā)生邏輯失效,這被稱作心跳超時;

2) 讀取和發(fā)送心跳消息的時候如何直接發(fā)生了IO異常,說明鏈路已經(jīng)失效,這被稱為心跳失敗。

無論發(fā)生心跳超時還是心跳失敗,都需要關(guān)閉鏈路,由客戶端發(fā)起重連操作,保證鏈路能夠恢復(fù)正常。

Netty的心跳檢測實際上是利用了鏈路空閑檢測機制實現(xiàn)的,相關(guān)代碼如下:

圖2-21 心跳檢測的代碼包路徑

Netty提供的空閑檢測機制分為三種:

1) 讀空閑,鏈路持續(xù)時間t沒有讀取到任何消息;

2) 寫空閑,鏈路持續(xù)時間t沒有發(fā)送任何消息;

3) 讀寫空閑,鏈路持續(xù)時間t沒有接收或者發(fā)送任何消息。

Netty的默認(rèn)讀寫空閑機制是發(fā)生超時異常,關(guān)閉連接,但是,我們可以定制它的超時實現(xiàn)機制,以便支持不同的用戶場景。

WriteTimeoutHandler的超時接口如下:

圖2-22 寫超時

ReadTimeoutHandler的超時接口如下:

圖2-23 讀超時

讀寫空閑的接口如下:

圖2-24 讀寫空閑

利用Netty提供的鏈路空閑檢測機制,可以非常靈活的實現(xiàn)協(xié)議層的心跳檢測。在《Netty權(quán)威指南》中的私有協(xié)議棧設(shè)計和開發(fā)章節(jié),我利用Netty提供的自定義Task接口實現(xiàn)了另一種心跳檢測機制,感興趣的朋友可以參閱該書。

2.3. Reactor線程的保護

Reactor線程是IO操作的核心,NIO框架的發(fā)動機,一旦出現(xiàn)故障,將會導(dǎo)致掛載在其上面的多路用復(fù)用器和多個鏈路無法正常工作。因此它的可靠性要求非常高。

筆者就曾經(jīng)遇到過因為異常處理不當(dāng)導(dǎo)致Reactor線程跑飛,大量業(yè)務(wù)請求處理失敗的故障。下面我們一起看下Netty是如何有效提升Reactor線程的可靠性的。

2.3.1. 異常處理要當(dāng)心

盡管Reactor線程主要處理IO操作,發(fā)生的異常通常是IO異常,但是,實際上在一些特殊場景下會發(fā)生非IO異常,如果僅僅捕獲IO異??赡芫蜁?dǎo)致Reactor線程跑飛。為了防止發(fā)生這種意外,在循環(huán)體內(nèi)一定要捕獲Throwable,而不是IO異?;蛘逧xception。

Netty的相關(guān)代碼如下:

圖2-25 Reactor線程異常保護

捕獲Throwable之后,即便發(fā)生了意外未知對異常,線程也不會跑飛,它休眠1S,防止死循環(huán)導(dǎo)致的異常繞接,然后繼續(xù)恢復(fù)執(zhí)行。這樣處理的核心理念就是:

1) 某個消息的異常不應(yīng)該導(dǎo)致整條鏈路不可用;

2) 某條鏈路不可用不應(yīng)該導(dǎo)致其它鏈路不可用;

3) 某個進程不可用不應(yīng)該導(dǎo)致其它集群節(jié)點不可用。

2.3.2. 死循環(huán)保護

通常情況下,死循環(huán)是可檢測、可預(yù)防但是無法完全避免的。Reactor線程通常處理的都是IO相關(guān)的操作,因此我們重點關(guān)注IO層面的死循環(huán)。

JDK NIO類庫最著名的就是 epoll bug了,它會導(dǎo)致Selector空輪詢,IO線程CPU 100%,嚴(yán)重影響系統(tǒng)的安全性和可靠性。

SUN在JKD1.6 update18版本聲稱解決了該BUG,但是根據(jù)業(yè)界的測試和大家的反饋,直到JDK1.7的早期版本,該BUG依然存在,并沒有完全被修復(fù)。發(fā)生該BUG的主機資源占用圖如下:

圖2-26 epoll bug CPU空輪詢

SUN在解決該BUG的問題上不給力,只能從NIO框架層面進行問題規(guī)避,下面我們看下Netty是如何解決該問題的。

Netty的解決策略:

1) 根據(jù)該BUG的特征,首先偵測該BUG是否發(fā)生;

2) 將問題Selector上注冊的Channel轉(zhuǎn)移到新建的Selector上;

3) 老的問題Selector關(guān)閉,使用新建的Selector替換。

下面具體看下代碼,首先檢測是否發(fā)生了該BUG:

圖2-27 epoll bug 檢測

一旦檢測發(fā)生該BUG,則重建Selector,代碼如下:

圖2-28 重建Selector

重建完成之后,替換老的Selector,代碼如下:

圖2-29 替換Selector

大量生產(chǎn)系統(tǒng)的運行表明,Netty的規(guī)避策略可以解決epoll bug 導(dǎo)致的IO線程CPU死循環(huán)問題。

2.4. 優(yōu)雅退出

Java的優(yōu)雅停機通常通過注冊JDK的ShutdownHook來實現(xiàn),當(dāng)系統(tǒng)接收到退出指令后,首先標(biāo)記系統(tǒng)處于退出狀態(tài),不再接收新的消息,然后將積壓的消息處理完,最后調(diào)用資源回收接口將資源銷毀,最后各線程退出執(zhí)行。

通常優(yōu)雅退出有個時間限制,例如30S,如果到達執(zhí)行時間仍然沒有完成退出前的操作,則由監(jiān)控腳本直接kill -9 pid,強制退出。

Netty的優(yōu)雅退出功能隨著版本的優(yōu)化和演進也在不斷的增強,下面我們一起看下Netty5的優(yōu)雅退出。

首先看下Reactor線程和線程組,它們提供了優(yōu)雅退出接口。EventExecutorGroup的接口定義如下:

圖2-30 EventExecutorGroup優(yōu)雅退出

NioEventLoop的資源釋放接口實現(xiàn):

圖2-31 NioEventLoop資源釋放

ChannelPipeline的關(guān)閉接口:

圖2-32 ChannelPipeline關(guān)閉接口

目前Netty向用戶提供的主要接口和類庫都提供了資源銷毀和優(yōu)雅退出的接口,用戶的自定義實現(xiàn)類可以繼承這些接口,完成用戶資源的釋放和優(yōu)雅退出。

2.5. 內(nèi)存保護

2.5.1. 緩沖區(qū)的內(nèi)存泄漏保護

為了提升內(nèi)存的利用率,Netty提供了內(nèi)存池和對象池。但是,基于緩存池實現(xiàn)以后需要對內(nèi)存的申請和釋放進行嚴(yán)格的管理,否則很容易導(dǎo)致內(nèi)存泄漏。

如果不采用內(nèi)存池技術(shù)實現(xiàn),每次對象都是以方法的局部變量形式被創(chuàng)建,使用完成之后,只要不再繼續(xù)引用它,JVM會自動釋放。但是,一旦引入內(nèi)存池機制,對象的生命周期將由內(nèi)存池負(fù)責(zé)管理,這通常是個全局引用,如果不顯式釋放JVM是不會回收這部分內(nèi)存的。

對于Netty的用戶而言,使用者的技術(shù)水平差異很大,一些對JVM內(nèi)存模型和內(nèi)存泄漏機制不了解的用戶,可能只記得申請內(nèi)存,忘記主動釋放內(nèi)存,特別是JAVA程序員。

為了防止因為用戶遺漏導(dǎo)致內(nèi)存泄漏,Netty在Pipe line的尾Handler中自動對內(nèi)存進行釋放,相關(guān)代碼如下:

圖2-33 TailHandler的內(nèi)存回收操作

對于內(nèi)存池,實際就是將緩沖區(qū)重新放到內(nèi)存池中循環(huán)使用,代碼如下:

圖2-34 PooledByteBuf的內(nèi)存回收操作

2.5.2. 緩沖區(qū)內(nèi)存溢出保護

做過協(xié)議棧的讀者都知道,當(dāng)我們對消息進行解碼的時候,需要創(chuàng)建緩沖區(qū)。緩沖區(qū)的創(chuàng)建方式通常有兩種:

1) 容量預(yù)分配,在實際讀寫過程中如果不夠再擴展;

2) 根據(jù)協(xié)議消息長度創(chuàng)建緩沖區(qū)。

在實際的商用環(huán)境中,如果遇到畸形碼流攻擊、協(xié)議消息編碼異常、消息丟包等問題時,可能會解析到一個超長的長度字段。筆者曾經(jīng)遇到過類似問題,報文長度字段值竟然是2G多,由于代碼的一個分支沒有對長度上限做有效保護,結(jié)果導(dǎo)致內(nèi)存溢出。系統(tǒng)重啟后幾秒內(nèi)再次內(nèi)存溢出,幸好及時定位出問題根因,險些釀成嚴(yán)重的事故。

Netty提供了編解碼框架,因此對于解碼緩沖區(qū)的上限保護就顯得非常重要。下面,我們看下Netty是如何對緩沖區(qū)進行上限保護的:

首先,在內(nèi)存分配的時候指定緩沖區(qū)長度上限:

圖2-35 緩沖區(qū)分配器可以指定緩沖區(qū)最大長度

其次,在對緩沖區(qū)進行寫入操作的時候,如果緩沖區(qū)容量不足需要擴展,首先對最大容量進行判斷,如果擴展后的容量超過上限,則拒絕擴展:

圖2-35 緩沖區(qū)擴展上限保護

最后,在解碼的時候,對消息長度進行判斷,如果超過最大容量上限,則拋出解碼異常,拒絕分配內(nèi)存:

圖2-36 超出容量上限的半包解碼,失敗

圖2-37 拋出TooLongFrameException異常

2.6. 流量整形

大多數(shù)的商用系統(tǒng)都有多個網(wǎng)元或者部件組成,例如參與短信互動,會涉及到手機、基站、短信中心、短信網(wǎng)關(guān)、SP/CP等網(wǎng)元。不同網(wǎng)元或者部件的處理性能不同。為了防止因為浪涌業(yè)務(wù)或者下游網(wǎng)元性能低導(dǎo)致下游網(wǎng)元被壓垮,有時候需要系統(tǒng)提供流量整形功能。

下面我們一起看下流量整形(traffic shaping)的定義:流量整形(Traffic Shaping)是一種主動調(diào)整流量輸出速率的措施。一個典型應(yīng)用是基于下游網(wǎng)絡(luò)結(jié)點的TP指標(biāo)來控制本地流量的輸出。流量整形與流量監(jiān)管的主要區(qū)別在于,流量整形對流量監(jiān)管中需要丟棄的報文進行緩存——通常是將它們放入緩沖區(qū)或隊列內(nèi),也稱流量整形(Traffic Shaping,簡稱TS)。當(dāng)令牌桶有足夠的令牌時,再均勻的向外發(fā)送這些被緩存的報文。流量整形與流量監(jiān)管的另一區(qū)別是,整形可能會增加延遲,而監(jiān)管幾乎不引入額外的延遲。

流量整形的原理示意圖如下:

圖2-38 流量整形原理圖

作為高性能的NIO框架,Netty的流量整形有兩個作用:

1) 防止由于上下游網(wǎng)元性能不均衡導(dǎo)致下游網(wǎng)元被壓垮,業(yè)務(wù)流程中斷;

2) 防止由于通信模塊接收消息過快,后端業(yè)務(wù)線程處理不及時導(dǎo)致的“撐死”問題。

下面我們就具體學(xué)習(xí)下Netty的流量整形功能。

2.6.1. 全局流量整形

全局流量整形的作用范圍是進程級的,無論你創(chuàng)建了多少個Channel,它的作用域針對所有的Channel。

用戶可以通過參數(shù)設(shè)置:報文的接收速率、報文的發(fā)送速率、整形周期。相關(guān)的接口如下所示:

圖2-39 全局流量整形參數(shù)設(shè)置

Netty流量整形的原理是:對每次讀取到的ByteBuf可寫字節(jié)數(shù)進行計算,獲取當(dāng)前的報文流量,然后與流量整形閾值對比。如果已經(jīng)達到或者超過了閾值。則計算等待時間delay,將當(dāng)前的ByteBuf放到定時任務(wù)Task中緩存,由定時任務(wù)線程池在延遲delay之后繼續(xù)處理該ByteBuf。相關(guān)代碼如下:

圖2-40 動態(tài)計算當(dāng)前流量

如果達到整形閾值,則對新接收的ByteBuf進行緩存,放入線程池的消息隊列中,稍后處理,代碼如下:

圖2-41 緩存當(dāng)前的ByteBuf

定時任務(wù)的延時時間根據(jù)檢測周期T和流量整形閾值計算得來,代碼如下:

圖2-42 計算緩存等待周期

需要指出的是,流量整形的閾值limit越大,流量整形的精度越高,流量整形功能是可靠性的一種保障,它無法做到100%的精確。這個跟后端的編解碼以及緩沖區(qū)的處理策略相關(guān),此處不再贅述。感興趣的朋友可以思考下,Netty為什么不做到 100%的精確。

流量整形與流控的最大區(qū)別在于流控會拒絕消息,流量整形不拒絕和丟棄消息,無論接收量多大,它總能以近似恒定的速度下發(fā)消息,跟變壓器的原理和功能類似。

2.6.2. 單條鏈路流量整形

除了全局流量整形,Netty也支持但鏈路的流量整形,相關(guān)的接口定義如下:

圖2-43 單鏈路流量整形

單鏈路流量整形與全局流量整形的最大區(qū)別就是它以單個鏈路為作用域,可以對不同的鏈路設(shè)置不同的整形策略。

它的實現(xiàn)原理與全局流量整形類似,我們不再贅述。值得說明的是,Netty支持用戶自定義流量整形策略,通過繼承AbstractTrafficShapingHandler的doAccounting方法可以定制整形策略。相關(guān)接口定義如下:

圖2-44 定制流量整形策略

3. 總結(jié)

盡管Netty在架構(gòu)可靠性上面已經(jīng)做了很多精細化的設(shè)計,以及基于防御式編程對系統(tǒng)進行了大量可靠性保護。但是,系統(tǒng)的可靠性是個持續(xù)投入和改進的過程,不可能在一個版本中一蹴而就,可靠性工作任重而道遠。

從業(yè)務(wù)的角度看,不同的行業(yè)、應(yīng)用場景對可靠性的要求也是不同的,例如電信行業(yè)的可靠性要求是5個9,對于鐵路等特殊行業(yè),可靠性要求更高,達到6個9。對于企業(yè)的一些邊緣IT系統(tǒng),可靠性要求會低些。

可靠性是一種投資,對于企業(yè)而言,追求極端可靠性對研發(fā)成本是個沉重的包袱,但是相反,如果不重視系統(tǒng)的可靠性,一旦不幸遭遇網(wǎng)上事故,損失往往也是驚人的。

對于架構(gòu)師和設(shè)計師,如何權(quán)衡架構(gòu)的可靠性和其它特性的關(guān)系,是一個很大的挑戰(zhàn)。通過研究和學(xué)習(xí)Netty的可靠性設(shè)計,也許能夠給大家?guī)硪恍﹩⑹尽?/p>

4. Netty學(xué)習(xí)推薦書籍

目前市面上介紹netty的文章很多,如果讀者希望系統(tǒng)性的學(xué)習(xí)Netty,推薦兩本書:

1) 《Netty in Action》

2) 《Netty權(quán)威指南》

5.作者簡介

李林鋒,2007年畢業(yè)于東北大學(xué),2008年進入華為公司從事高性能通信軟件的設(shè)計和開發(fā)工作,有6年NIO設(shè)計和開發(fā)經(jīng)驗,精通Netty、Mina等NIO框架。Netty中國社區(qū)創(chuàng)始人,《Netty權(quán)威指南》作者。


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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多