netty可靠性

 Netty的可靠性

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

1) 構建RPC調用的基礎通訊組件,提供跨節點的遠程服務調用能力;緩存

2) NIO通訊框架,用於跨節點的數據交換;安全

3) 其它應用協議棧的基礎通訊組件,例如HTTP協議以及其它基於Netty開發的應用層協議棧。網絡

以阿里的分佈式服務框架Dubbo爲例,Netty是Dubbo RPC框架的核心。它的服務調用示例圖以下:框架

圖1-1 Dubbo的節點角色說明圖運維

其中,服務提供者和服務調用者之間能夠經過Dubbo協議進行RPC調用,消息的收發默認經過Netty完成。異步

經過對Netty主流應用場景的分析,咱們發現Netty面臨的可靠性問題大體分爲三類:分佈式

1) 傳統的網絡I/O故障,例如網絡閃斷、防火牆Hang住鏈接、網絡超時等;oop

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

3) 編解碼相關的異常。

在大多數的業務應用場景中,一旦由於某些故障致使Netty不能正常工做,業務每每會陷入癱瘓。因此,從業務訴求來看,對Netty框架的可靠性要求是很是的高。做爲當前業界最流行的一款NIO框架,Netty在不一樣行業和領域都獲得了普遍的應用,它的高可靠性已經獲得了成百上千的生產系統檢驗。

Netty是如何支持系統高可靠性的?下面,咱們就從幾個不一樣維度出發一探究竟。

2. Netty高可靠性之道

2.1. 網絡通訊類故障

2.1.1. 客戶端鏈接超時

在傳統的同步阻塞編程模式下,客戶端Socket發起網絡鏈接,每每須要指定鏈接超時時間,這樣作的目的主要有兩個:

1) 在同步阻塞I/O模型中,鏈接操做是同步阻塞的,若是不設置超時時間,客戶端I/O線程可能會被長時間阻塞,這會致使系統可用I/O線程數的減小;

2) 業務層須要:大多數系統都會對業務流程執行時間有限制,例如WEB交互類的響應時間要小於3S。客戶端設置鏈接超時時間是爲了實現業務層的超時。

JDK原生的Socket鏈接接口定義以下:

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

對於NIO的SocketChannel,在非阻塞模式下,它會直接返回鏈接結果,若是沒有鏈接成功,也沒有發生IO異常,則須要將SocketChannel註冊到Selector上監聽鏈接結果。因此,異步鏈接的超時沒法在API層面直接設置,而是須要經過定時器來主動監測。

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

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

從上面的接口定義能夠看出,NIO類庫並無現成的鏈接超時接口供用戶直接使用,若是要在NIO編程中支持鏈接超時,每每須要NIO框架或者用戶本身封裝實現。

下面咱們看下Netty是如何支持鏈接超時的,首先,在建立NIO客戶端的時候,能夠配置鏈接超時參數:

圖2-3 Netty客戶端建立支持設置鏈接超時參數

設置完鏈接超時以後,Netty在發起鏈接的時候,會根據超時時間建立ScheduledFuture掛載在Reactor線程上,用於定時監測是否發生鏈接超時,相關代碼以下:

圖2-4 根據鏈接超時建立超時監測定時任務

建立鏈接超時定時任務以後,會由NioEventLoop負責執行。若是已經鏈接超時,可是服務端仍然沒有返回TCP握手應答,則關閉鏈接,代碼如上圖所示。

若是在超時期限內處理完成鏈接操做,則取消鏈接超時定時任務,相關代碼以下:

圖2-5 取消鏈接超時定時任務

Netty的客戶端鏈接超時參數與其它經常使用的TCP參數一塊兒配置,使用起來很是方便,上層用戶不用關心底層的超時實現機制。這既知足了用戶的個性化需求,又實現了故障的分層隔離。

2.1.2. 通訊對端強制關閉鏈接

在客戶端和服務端正常通訊過程當中,若是發生網絡閃斷、對方進程忽然宕機或者其它非正常關閉鏈路事件時,TCP鏈路就會發生異常。因爲TCP是全雙工的,通訊雙方都須要關閉和釋放Socket句柄纔不會發生句柄的泄漏。

在實際的NIO編程過程當中,咱們常常會發現因爲句柄沒有被及時關閉致使的功能和可靠性問題。究其緣由總結以下:

1) IO的讀寫等操做並不是僅僅集中在Reactor線程內部,用戶上層的一些定製行爲可能會致使IO操做的外逸,例如業務自定義心跳機制。這些定製行爲加大了統一異常處理的難度,IO操做愈加散,故障發生的機率就越大;

2) 一些異常分支沒有考慮到,因爲外部環境誘因致使程序進入這些分支,就會引發故障。

下面咱們經過故障模擬,看Netty是如何處理對端鏈路強制關閉異常的。首先啓動Netty服務端和客戶端,TCP鏈路創建成功以後,雙方維持該鏈路,查看鏈路狀態,結果以下:

圖2-6 Netty服務端和客戶端TCP鏈路狀態正常

強制關閉客戶端,模擬客戶端宕機,服務端控制檯打印以下異常:

圖2-7 模擬TCP鏈路故障

從堆棧信息能夠判斷,服務端已經監控到客戶端強制關閉了鏈接,下面咱們看下服務端是否已經釋放了鏈接句柄,再次執行netstat命令,執行結果以下:

圖2-8 查看故障鏈路狀態

從執行結果能夠看出,服務端已經關閉了和客戶端的TCP鏈接,句柄資源正常釋放。由此能夠得出結論,Netty底層已經自動對該故障進行了處理。

下面咱們一塊兒看下Netty是如何感知到鏈路關閉異常並進行正確處理的,查看AbstractByteBuf的writeBytes方法,它負責將指定Channel的緩衝區數據寫入到ByteBuf中,詳細代碼以下:

圖2-9 AbstractByteBuf的writeBytes方法

在調用SocketChannel的read方法時發生了IOException,代碼以下:

圖2-10 讀取緩衝區數據發生IO異常

爲了保證IO異常被統一處理,該異常向上拋,由AbstractNioByteChannel進行統一異常處理,代碼以下:

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

爲了可以對異常策略進行統一,也爲了方便維護,防止處理不當致使的句柄泄漏等問題,句柄的關閉,統一調用AbstractChannel的close方法,代碼以下:

圖2-12 統一的Socket句柄關閉接口

2.1.3. 正常的鏈接關閉

對於短鏈接協議,例如HTTP協議,通訊雙方數據交互完成以後,一般按照雙方的約定由服務端關閉鏈接,客戶端得到TCP鏈接關閉請求以後,關閉自身的Socket鏈接,雙方正式斷開鏈接。

在實際的NIO編程過程當中,常常存在一種誤區:認爲只要是對方關閉鏈接,就會發生IO異常,捕獲IO異常以後再關閉鏈接便可。實際上,鏈接的合法關閉不會發生IO異常,它是一種正常場景,若是遺漏了該場景的判斷和處理就會致使鏈接句柄泄漏。

下面咱們一塊兒模擬故障,看Netty是如何處理的。測試場景設計以下:改造下Netty客戶端,雙發鏈路創建成功以後,等待120S,客戶端正常關閉鏈路。看服務端是否可以感知並釋放句柄資源。

首先啓動Netty客戶端和服務端,雙方TCP鏈路鏈接正常:

圖2-13 TCP鏈接狀態正常

120S以後,客戶端關閉鏈接,進程退出,爲了可以看到整個處理過程,咱們在服務端的Reactor線程處設置斷點,先不作處理,此時鏈路狀態以下:

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

從上圖能夠看出,此時服務端並無關閉Socket鏈接,鏈路處於CLOSE_WAIT狀態,放開代碼讓服務端執行完,結果以下:

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

下面咱們一塊兒看下服務端是如何判斷出客戶端關閉鏈接的,當鏈接被對方合法關閉後,被關閉的SocketChannel會處於就緒狀態,SocketChannel的read操做返回值爲-1,說明鏈接已經被關閉,代碼以下:

圖2-16 須要對讀取的字節數進行判斷

若是SocketChannel被設置爲非阻塞,則它的read操做可能返回三個值:

1) 大於0,表示讀取到了字節數;

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

3) -1,鏈接已經被對方合法關閉。

經過調試,咱們發現,NIO類庫的返回值確實爲-1:

圖2-17 鏈路正常關閉,返回值爲-1

得知鏈接關閉以後,Netty將關閉操做位設置爲true,關閉句柄,代碼以下:

圖2-18 鏈接正常關閉,釋放資源

2.1.4. 故障定製

在大多數場景下,當底層網絡發生故障的時候,應該由底層的NIO框架負責釋放資源,處理異常等。上層的業務應用不須要關心底層的處理細節。可是,在一些特殊的場景下,用戶可能須要感知這些異常,並針對這些異常進行定製處理,例如:

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

2) 消息的緩存重發;

3) 接口日誌中詳細記錄故障細節;

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

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

具體接口定義以及默認實現以下:

圖2-19 故障定製接口

用戶能夠覆蓋該接口,進行個性化的異常定製。例如發起重連等。

相關文章
相關標籤/搜索