為什麼要學習Netty?

點九先生發表於2021-07-29

一、傳統的BIO程式設計

​ 網路程式設計的基本模型是 Client/Server 模型,也就是兩個程式之間進行相互通訊,其中服務端提供位置資訊(繫結的 IP 地址和監聽埠),客戶端通過連線操作向服務端監聽的地址發起連線請求,通過三次握手建立連
接,如果連線建立成功,雙方就可以通過網路套接字(Socket)進行通訊。
​ 在基於傳統同步阻塞模型開發中,ServerSocket 負責繫結 IP 地址,啟動監聽埠;Socket 負責發起連線操作。連線成功之後,雙方通過輸入和輸出流進行同步阻塞式通訊。

​ 首先,我們通過下圖所示的通訊模型圖來熟悉下 BIO 的服務端通訊模型:採用 BIO 通訊模型的服務端,通常由一個獨立的 Acceptor 執行緒負責監聽客戶端的連線,它接收到客戶端連線請求之後為每個客戶端建立一個新的執行緒進行鏈路處理,處理完成之後,通過輸出流返回應答給客戶端,執行緒銷燬。這就是典型的一請求一應答通訊模型

image-20210721163910910

​ 該模型最大的問題就是缺乏彈性伸縮能力,當客戶端併發訪問量增加後,服務端的執行緒個數和客戶端併發訪問數呈 1:1 的正比關係,由於執行緒是 Java 虛擬機器非常寶貴的系統資源,當執行緒數膨脹之後,系統的效能將急劇下降,隨著併發訪問量的繼續增大,系統會發生執行緒堆疊溢位、建立新執行緒失敗等問題,並最終導致程式當機或者僵死,不能對外提供服務。

二、偽非同步的I/O程式設計

​ 為了解決同步阻塞 I/O 面臨的一個鏈路需要一個執行緒處理的問題,後來有人對它的執行緒模型進行了優化,後端通過一個執行緒池來處理多個客戶端的請求接入,形成客戶端個數 M:執行緒池最大執行緒數 N 的比例關係,其中 M 可以遠遠大於 N,通過執行緒池可以靈活的調配執行緒資源,設定執行緒的最大值,防止由於海量併發接入導致執行緒耗盡。

​ 當有新的客戶端接入的時候,將客戶端的 Socket 封裝成一個 Task(該任務實現 java.lang.Runnable 介面)投遞到後端的執行緒池中進行處理,JDK 的執行緒池維護一個訊息佇列和 N 個活躍執行緒對訊息佇列中的任務進行處理。由於執行緒池可以設定訊息佇列的大小和最大執行緒數,因此,它的資源佔用是可控的,無論多少個客戶端併發訪問,都不會導致資源的耗盡和當機。

image-20210729140051005

​ 偽非同步 I/O 實際上僅僅只是對之前 I/O 執行緒模型的一個簡單優化,它無法從根本上解決同步 I/O 導致的通訊執行緒阻塞問題。下面我們就簡單分析下如果通訊對方返回應答時間過長,會引起的級聯故障。

  1. 服務端處理緩慢,返回應答訊息耗費60s,平時只需要10ms。

  2. 採用偽非同步I/O的執行緒正在讀取故障服務節點的響應,由於讀取輸入流是阻塞的,因此,它將會被同步阻塞60s。

  3. 假如所有的可用執行緒都被故障伺服器阻塞,那後續所有的I/O訊息都將在佇列中排隊。

  4. 由於執行緒池採用阻塞佇列實現,當佇列積滿之後,後續入佇列的操作將被阻塞。

  5. 由於前端只有一個Accptor執行緒接收客戶端接入,它被阻塞線上程池的同步阻塞佇列之後,新的客戶端請求訊息將被拒絕,客戶端會發生大量的連線超時。

  6. 由於幾乎所有的連線都超時,呼叫者會認為系統已經崩潰,無法接收新的請求訊息。

三、NIO程式設計

​ NIO即New I/O,即較java傳統支援的I/O是新增的非阻塞I/O。也被稱為Non-block I/O。

​ 與 Socket 類和 ServerSocket 類相對應,NIO 也提供了 SocketChannel 和ServerSocketChannel 兩種不同的套接字通道實現。這兩種新增的通道都支援阻塞和非阻塞兩種模式。阻塞模式使用非常簡單,但是效能和可靠性都不好,非阻塞模式則正好相反。開發人員一般可以根據自己的需要來選擇合適的模式,一般來說,低負載、低併發的應用程式可以選擇同步阻塞 I/O 以降低程式設計複雜度,但是對於高負載、高併發的網路應用,需要使用 NIO 的非阻塞模式進行開發。

四、AIO程式設計

NIO2.0 引入了新的非同步通道的概念,並提供了非同步檔案通道和非同步套接字通道的實現。非同步通道提供兩種方式獲取獲取操作結果:

  • 通過java.util.concurrent.Future類來表示非同步操作的結果;

  • 在執行非同步操作的時候傳入一個java.nio.channels;

  • CompletionHandler介面的實現類作為操作完成的回撥。

NIO2.0 的非同步套接字通道是真正的非同步非阻塞 I/O,它對應 UNIX 網路程式設計中的事件驅動 I/O(AIO),它不需要通過多路複用器(Selector)對註冊的通道進行輪詢操作即可實現非同步讀寫,從而簡化了 NIO 的程式設計模型。

五、I/O模型對比

image-20210729142757667

六、主流NIO框架

​ 目前,業界主流的 NIO 框架主要有兩款:Mina 和 Netty,兩者都使用 Apache LICENSE-2.0 進行開源。不同之處是Mina 是 Apache 基金會的官方 NIO 框架,Netty 之前是 Jboss 的 NIO 框架,後來脫離 Jboss 獨立申請了 netty.io 域名,與 Jboss 脫離關係,並對版本進行了重構,導致 API 無法向上相容。

​ Mina 和 Netty 還 有 一 段 歷 史 淵 源,Mina 最 初 版 本 的 架 構 師 是 Trustin Lee,後來,由於種種原因,Trustin Lee 離開了 Mina 社群加入到了 Netty 團隊,重新設計並開發了 Netty。很多讀者會發現 Netty 中透著 Mina 的影子,兩個框架的架構理念也有很多相似之處,甚至一些程式碼都非常相似,原因就在這裡。

​ 目前,Mina 和 Netty 的應用已經非常廣泛,很多開源框架都使用兩者做底層的 NIO 框架,例如 Hadoop 的通訊元件 Avro 使用 Netty 做底層的通訊框架,Openfire 則使用 Mina 做底層通訊框架,相比於 Mina,Netty 社群目前更活躍,版本應用範圍也更廣。

七、為什麼學習Netty

7.1 不選擇Java原生NIO的原因

  1. NIO的類庫和API繁雜,使用麻煩,你需要熟練掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。

  2. 需要具備其他的額外技能做鋪墊,例如熟悉Java多執行緒程式設計。這是因為NIO程式設計涉及到Reactor模式,你必須對多執行緒和網路程式設計非常熟悉,才能編寫出高質量的NIO程式。

  3. 可靠效能力補齊,工作量和難度都非常大。例如客戶端面臨斷連重連、網路閃斷、半包讀寫、失敗快取、網路擁塞和異常碼流的處理等問題,NIO程式設計的特點是功能開發相對容易,但是可靠效能力補齊的工作量和難度都非常大

7.2 選擇Netty的理由

  • API使用簡單,開發門檻低;

  • 功能強大,預置了多種編解碼功能,支援多種主流協議;

  • 定製能力強,可以通過ChannelHandler對通訊框架進行靈活地擴充套件;

  • 效能高,通過與其他業界主流的NIO框架對比,Netty的綜合效能最優;

  • 成熟、穩定,Netty修復了已經發現的所有JDK NIO BUG,業務開發人員不需要再為NIO的BUG而煩惱;

  • 社群活躍,版本迭代週期短,發現的BUG可以被及時修復,同時,更多的新功能會加入;

  • 經歷了大規模的商業應用考驗,質量得到驗證。在網際網路、大資料、網路遊戲、企業應用、電信軟體等眾多行業得到成功商用,證明了它已經完全能夠滿足不同行業的商業應用了。

八、Netty開發環境搭建

8.1 Netty 的官網

Netty: Home

8.2 Maven倉庫地址

https://mvnrepository.com/artifact/io.netty/netty-all

8.3 Maven依賴

<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.66.Final</version>
</dependency>

8.4 Gradle依賴

// https://mvnrepository.com/artifact/io.netty/netty-all
implementation group: 'io.netty', name: 'netty-all', version: '4.1.66.Final'

相關文章