02.25 Netty基礎知識整理

Netty的定義

Netty 是一個高性能、異步事件驅動的 NIO 框架,用於快速開發可維護的高性能協議服務器和客戶端,基於 JAVA NIO 提供的 API 實現。它提供了對TCP、UDP 和文件傳輸的支持,作為一個異步 NIO 框架,Netty 的所有 IO 操作都是異步非阻塞的,通過 Future-Listener 機制,用戶可以方便的主動獲取或者通過通知機制獲得 IO 操作結果。

為什麼使用Netty

  1. 高性能:吞吐量更高,延遲更低,資源消耗減少,最小化不必要的內存複製,與目前多種NIO主流框架相比,Netty綜合性能最高。
  2. 高穩定性:Netty修復了已經發現的所有JDK NIO中的BUG。比如epoll bug,這個BUG會在linux上導致cpu 100%,使得nio server/client不可用,這個BUG直到jdk 6u4才解決,但是直到JDK1.7中仍然有這個問題,該問題並未被完全解決,只是發生的頻率降低了而已。
  3. 定製能力高:可以通過ChannelHandler對通信框架進行靈活地拓展。
  4. 功能強大:預置了多種編解碼功能,支持多種主流協議。
  5. 設計優雅:統一的API,適用於不同的協議(阻塞和非阻塞);基於靈活、可擴展的事件驅動模型;高度可定製的線程模型;可靠的無連接數據Socket支持(UDP)。
  6. 經歷了大規模的商業應用考驗,質量和可靠性都有很好的驗證。

Netty能夠做什麼

  1. 作為基礎通信組件被這些 RPC 框架使用,Dubbo 的 RPC 框架使用 Dubbo 協議進行節點間通信,Dubbo 協議默認使用 Netty 作為基礎通信組件,用於實現各進程節點之間的內部通信。
  2. 大數據計算往往採用多個計算節點和一個/N個彙總節點進行分佈式部署,各節點之間存在海量的數據交換。由於 Netty 的綜合性能是目前各個成熟 NIO 框架中最高的,因此,往往會被選中用作大數據各節點間的通信。

Netty整體結構


Netty基礎知識整理

Netty結構圖

Netty由核心(Core)、傳輸服務(Transport Servies)以及協議支持(Protocol Support)這幾個模塊組成。核心模塊提供了性能極高的零拷貝能力,還提供了統一的通信API和可高度擴展的事件驅動模型。傳輸服務模塊和協議支持模塊是對 Netty 的有力補充。傳輸服務模塊支持了TCP和UDP等Socket通信,以及HTTP和同一JVM內的通信通道。協議支持模塊則對常見的序列化協議進行支持,如Protobuf、gzip等。

Netty組件


Netty基礎知識整理

Channel

Channel 是 Java NIO 的一個基本構造。 它代表一個到實體(如一個硬件設備、一個文件、一個網絡套接字或者一個能夠執行一個或者多個不同的I/O操作的程序組件)的開放連接,如讀操作和寫操作。 目前,可以把 Channel 看作是傳入(入站)或者傳出(出站)數據的載體。因此,它可以被打開或者被關閉,連接或者斷開連接。

回調

一個回調其實就是一個方法,一個指向已經被提供給另外一個方法的方法的引用。Netty 在內部使用了回調來處理事件;當一個回調被觸發時,相關的事件可以被一個ChannelHandler 的實現處理。例如當一個新的連接已經被建立時, ChannelHandler 的 channelActive()回調方法將會被調用。

ChannelFuture

Future 提供了另一種在操作完成時通知應用程序的方式。這個對象可以看作是一個異步操作的結果的佔位符; 它將在未來的某個時刻完成,並提供對其結果的訪問。Netty的ChannelFuture,用於在執行異步操作的時候使用。

ChannelFuture接口的addListener()方法註冊了一個ChannelFutureListener,以便在某個操作完成時(無論是否成功)得到通知。監聽器的回調方法operationComplete(),將會在對應的操作完成時被調用 。然後監聽器可以判斷該操作是成功地完成了還是出錯了。每個 Netty 的出站 I/O 操作都將返回一個 ChannelFuture,不會阻塞,所以Netty完全是異步和事件驅動的。

可以把ChannelFutureListener 看作是回調的一個更加精細的版本,回調和 Future 是相互補充的機制;它們相互結合,構成了 Netty 本身的關鍵構件塊之一。

Netty 的異步編程模型是建立在 Future 和回調的概念之上的。

EventLoop

控制流、多線程處理、併發;

EventLoop 定義了 Netty 的核心抽象,用於處理連接的生命週期中所發生的事件。

Netty 通過觸發事件將 Selector 從應用程序中抽象出來,消除了所有本來將需要手動編寫的派發代碼。在內部,將會為每個Channel分配一個 EventLoop,用以處理所有事件,包括: 註冊感興趣的事件,將事件派發給 ChannelHandler,以及安排進一步的動作。
EventLoop 本身只由一個線程驅動,其處理了一個 Channel 的所有 I/O 事件,並且在該EventLoop 的整個生命週期內都不會改變。這個簡單而強大的設計消除了你可能有的在 ChannelHandler 實現中需要進行同步的任何顧慮,因此,你可以專注於提供正確的邏輯,用來在有感興趣的數據要處理的時候執行。

EventLoopGroup

一個 EventLoopGroup 包含一個或者多個 EventLoop;
一個 EventLoop 在它的生命週期內只和一個 Thread 綁定;
所有由 EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理;
一個 Channel 在它的生命週期內只註冊於一個 EventLoop;
一個 EventLoop 可能會被分配給一個或多個 Channel。 在這種設計中,一個給定 Channel 的 I/O 操作都是由相同的 Thread 執行的,實際上消除了對於同步的需要。


Netty基礎知識整理

包含入站和出站 ChannelHandler的ChannelPipeline

事件和ChannelHandler

Netty 使用不同的事件來通知我們狀態的改變或者是操作的狀態。這使得我們能夠基於已經 發生的事件來觸發適當的動作。這些動作可能是: 記錄日誌、數據轉換、流控制或者應用程序邏輯。Netty 是一個網絡編程框架,所以事件是按照它們與入站或出站數據流的相關性進行分類的。

可能由入站數據或者相關的狀態更改而觸發的事件包括: 連接已被激活或者連接失活,異步地連接到遠程節點,數據讀取,用戶事件或者錯誤事件。

出站事件是未來將會觸發的某個動作的操作結果,這些動作包括: 打開或者關閉到遠程節點的連接,將數據寫到或者沖刷到套接字。

Netty 的主要組件是 ChannelHandler,它充當了所有處理入站和出站數據的應用程序邏輯的容器。事實上,ChannelHandler 可專門用於幾乎任何類型的動作,例如將數據從一種格式轉換為另外一種格式,或者處理轉換過程 中所拋出的異常。

使得事件流經 ChannelPipeline 是 ChannelHandler 的工作,它們是在應用程序的初 始化或者引導階段被安裝的。這些對象接收事件、執行它們所實現的處理邏輯,並將數據傳遞給鏈中的下一個 ChannelHandler。它們的執行順序是由它們被添加的順序所決定的。

ChannelPipeline

ChannelPipeline 提供了 ChannelHandler 鏈的容器,並定義了用於在該鏈上傳播入站 和出站事件流的 API。當 Channel 被創建時,它會被自動地分配到它專屬的 ChannelPipeline。

ChannelHandler 安裝到 ChannelPipeline 中的過程如下所示:

  1. 一個ChannelInitializer的實現被註冊到了ServerBootstrap中;
  2. 當 ChannelInitializer.initChannel()方法被調用時,ChannelInitializer將在 ChannelPipeline 中安裝一組自定義的ChannelHandler;
  3. ChannelInitializer 將它自己從 ChannelPipeline 中移除。

如果一個消息或者任何其他的入站事件被讀取,那麼它會從 ChannelPipeline 的頭部開始流動,並被傳遞給第一個 ChannelInboundHandler。這個 ChannelHandler不一定會實際地修改數據,具體取決於它的具體功能,在這之後,數據將會被傳遞給鏈中的下一個ChannelInboundHandler。最終,數據將會到達ChannelPipeline的尾端,屆時,所有處理就都結束了。

數據的出站運動(即正在被寫的數據)在概念上也是一樣的。在這種情況下,數據將從ChannelOutboundHandler鏈的尾端開始流動,直到它到達鏈的頭部為止。在這之後,出站數據將會到達網絡傳輸層,這裡顯示為 Socket。通常情況下,這將觸發一個寫操作。

當 ChannelHandler 被添加到 ChannelPipeline 時,它將會被分配一個 ChannelHandlerContext,其代表了 ChannelHandler 和 ChannelPipeline 之間的綁定。雖然這個對象可以被用於獲取底層的 Channel,但是它主要還是被用於寫出站數據。

ChannelPipeline 中的每個ChannelHandler將負責把事件轉發到鏈中的下一個ChannelHandler。通過使用作為參數傳遞到每個方法的ChannelHandlerContext,事件可以被傳遞給當前ChannelHandler 鏈中的下一個 ChannelHandler。

在Netty中,有兩種發送消息的方式。你可以直接寫到Channel中,也可以 寫到和ChannelHandler 相關聯的 ChannelHandlerContext 對象中。前一種方式將會導致消息從 ChannelPipeline 的尾端開始流動,而後者將導致消息從 ChannelPipeline 中的下一個 ChannelHandler 開始流動。

編碼器和解碼器

Netty提供的編碼器/解碼器適配器類都實現了ChannelOutboundHandler或者ChannelInboundHandler 接口。對於入站數據來說,channelRead 方法/事件已經被重寫了。對於每個從入站 Channel 讀取的消息,這個方法都將會被調用。隨後,它將調用由預置解碼器所提供的 decode() 方法,並將已解碼的字節轉發給 ChannelPipeline 中的下一個 ChannelInboundHandler。

出站消息的模式是相反方向的:編碼器將消息轉換為字節,並將它們轉發給下一個 ChannelOutboundHandler。

BootStrap

Netty 的引導類為應用程序的網絡層配置提供了容器,這涉及將一個進程綁定到某個指定的端口,或者將一個進程連接到另一個運行在某個指定主機的指定端口上的進程。

ServerBootstrap 將綁定到一個 端口,因為服務器必須要監聽連接,而 Bootstrap 則是由想要連接到遠程節點的客戶端應用程序所使用的。

服務器需要兩組不同的 Channel。第一組將只包含一個 ServerChannel,代表服務 器自身的已綁定到某個本地端口的正在監聽的套接字。而第二組將包含所有已創建的用來處理傳 入客戶端連接(對於每個服務器已經接受的連接都有一個)的Channel。

ByteBuf

Netty ByteBuff的優點:

可以被用戶自定義的緩衝區類型擴展;

通過內置的複合緩衝區類型實現了透明的零拷貝;

容量可以按需增長(類似於 JDK 的 StringBuilder);

在讀和寫這兩種模式之間切換不需要調用 ByteBuffer 的 flip()方法;

讀和寫使用了不同的索引;

支持方法的鏈式調用;

支持引用計數;

支持池化。

Netty服務端創建流程

Netty基礎知識整理

Netty服務端創建流程


IO模型對比


Netty基礎知識整理

5種IO模型


阻塞IO

在用戶進程發起 I/O 操作後,需要等待操作完成才能繼續運行。

非阻塞IO

在用戶進程發起I/O操作後,無須等待操作完成即可繼續進行其他操作,但用戶進程需要定期詢問I/O操作是否就緒。可以使用一個線程監聽所有的 Socket請求,從而極大地減少線程數量。

判斷阻塞I/O與非阻塞I/O時應關注程序是否在等待調用結果——如果系統內核中的數據還未準備完成,用戶進程是繼續等待直至準備完成,還是直接返回並先處理其他事情。

同步IO

在系統內核準備好處理數據後,還需要等待內核將數據複製到用戶進程,才能進行處理。

異步IO

用戶進程無須關心實際I/O的操作過程,只需在I/O完成後由內核接收通知,I/O操作全部由內核進程來執行。

判斷是同步I/O還是異步I/O,主要關注內核數據複製到用戶空間時是否需要等待。

BIO同步阻塞IO

BIO 服務器實現模式為每一個連接都分配了一個線程,即客戶端有連接請求時,服務端就需要啟動一個線程進行處理。它缺乏彈性伸縮能力,服務端的線程個數和客戶端併發訪問數呈正比,隨著訪問量的增加,線程數量會迅速膨脹,最終導致系統性能急劇下降。可以通過合理使用線程池來改進“一連接一線程”模型,實現一個線程處理多個客戶端,但開啟線程的數量終歸會受到系統資源的限制,而且頻繁進行線程上下文切換也會導致CPU的利用率降低。

NIO同步非阻塞IO

NIO通過事件模型的異步通知機制去處理輸入/輸出的相關操作。在客戶端的連接建立完畢且讀取準備就緒後,位於服務端的連接接收器便會觸發相關事件。與BIO不同,NIO的一切處理都是通過事件驅動的,客戶端連接到服務端並創建通信管道,服務端會將通信管道註冊到事件選擇器,由事件選擇器接管事件的監聽,並派發至工作線程進行讀取、編解碼、計算以及發送。

一個Selector可以同時註冊、監聽和輪詢成百上千個Channel,一個用於處理I/O的線程可以同時併發處理多個客戶端連接,具體數量取決於進程可用的最大文件句柄數。由於處理 I/O的線程數大幅減少,因此CPU用於處理線程切換和競爭的時間也相應減少,即NIO中的CPU利用率比BIO中的CPU利用率大幅提高

參考銜接

  1. https://blog.csdn.net/xu_melon/article/details/79201198
  2. https://blog.csdn.net/qq_35571554/article/details/82786257
  3. https://www.cnblogs.com/chenxiaoxian/p/10431765.html
  4. https://segmentfault.com/a/1190000017128263?utm_source=tag-newest
  5. Netty實戰書籍
  6. 未來架構:從服務化到雲原生


分享到:


相關文章: