為什麼要學習Netty?

一、傳統的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倉庫地址

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

8.3 Maven依賴

<!-- //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依賴

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