一步一圖,帶你走進 Netty 的世界!

來源:cnblogs.com/sessionbest/p/9000727.html


  1. Netty 是什麼 ============

Netty 是一個高性能、異步事件驅動的 NIO 框架,基於 JAVA NIO 提供的 API 實現。它提供了對 TCP、UDP 和文件傳輸的支持

作爲一個異步 NIO 框架,Netty 的所有 IO 操作都是異步非阻塞的,通過 Future-Listener 機制,用戶可以方便的主動獲取或者通過通知機制獲得 IO 操作結果。

作爲當前最流行的 NIO 框架,Netty 在互聯網領域、大數據分佈式計算領域、遊戲行業、通信行業等獲得了廣泛的應用,一些業界著名的開源組件也基於 Netty 的 NIO 框架構建。

  1. Netty 線程模型 =============

在 JAVA NIO 方面 Selector 給 Reactor 模式提供了基礎,Netty 結合 Selector 和 Reactor 模式設計了高效的線程模型。先來看下 Reactor 模式:

2.1 Reactor 模式

Wikipedia 這麼解釋 Reactor 模型:

“The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”。

首先 Reactor 模式首先是事件驅動 的,有一個或者多個併發輸入源,有一個 Server Handler 和多個 Request Handlers

這個 Service Handler 會同步的將輸入的請求多路複用的分發給相應的 Request Handler。

可以如下圖所示:

圖片

從結構上有點類似生產者和消費者模型,即一個或多個生產者將事件放入一個 Queue 中,而一個或者多個消費者主動的從這個隊列中 poll 事件來處理;

而 Reactor 模式則沒有 Queue 來做緩衝,每當一個事件輸入到 Service Handler 之後,該 Service Handler 會主動根據不同的 Evnent 類型將其分發給對應的 Request Handler 來處理。

2.2 Reator 模式的實現

關於 Java NIO 構造 Reator 模式,Doug lea 在《Scalable IO in Java》中給了很好的闡述,這裏截取 PPT 對 Reator 模式的實現進行說明

1. 第一種實現模型如下:

圖片

這是最簡單的 Reactor 單線程模型,由於 Reactor 模式使用的是異步非阻塞 IO,所有的 IO 操作都不會被阻塞,理論上一個線程可以獨立處理所有的 IO 操作。

這時 Reactor 線程是個多面手,負責多路分離套接字,Accept 新連接,並分發請求到處理鏈中。

對於一些小容量應用場景,可以使用到單線程模型。但對於高負載,大併發的應用卻不合適,主要原因如下:

  1. 當一個 NIO 線程同時處理成百上千的鏈路,性能上無法支撐,即使 NIO 線程的 CPU 負荷達到 100%,也無法完全處理消息

  2. 當 NIO 線程負載過重後,處理速度會變慢,會導致大量客戶端連接超時,超時之後往往會重發,更加重了 NIO 線程的負載。

  3. 可靠性低,一個線程意外死循環,會導致整個通信系統不可用

爲了解決這些問題,出現了 Reactor 多線程模型。

2.Reactor 多線程模型:

圖片

相比上一種模式,該模型在處理鏈部分採用了多線程(線程池)。

在絕大多數場景下,該模型都能滿足性能需求。但是,在一些特殊的應用場景下,如服務器會對客戶端的握手消息進行安全認證。這類場景下,單獨的一個 Acceptor 線程可能會存在性能不足的問題。

爲了解決這些問題,產生了第三種 Reactor 線程模型

3.Reactor 主從模型

圖片

該模型相比第二種模型,是將 Reactor 分成兩部分,mainReactor 負責監聽 server socket,accept 新連接;並將建立的 socket 分派給 subReactor。

subReactor 負責多路分離已連接的 socket,讀寫網絡數據,對業務處理功能,其扔給 worker 線程池完成。通常,subReactor 個數上可與 CPU 個數等同。

2.3 Netty 模型

2.2 中說完了 Reactor 的三種模型,那麼 Netty 是哪一種呢?

其實 Netty 的線程模型是 Reactor 模型的變種,那就是去掉線程池的第三種形式的變種,這也是 Netty NIO 的默認模式。

Netty 中 Reactor 模式的參與者主要有下面一些組件:

  1. Selector

  2. EventLoopGroup/EventLoop

  3. ChannelPipeline

Selector 即爲 NIO 中提供的 SelectableChannel 多路複用器,充當着 demultiplexer 的角色,這裏不再贅述;下面對另外兩種功能和其在 Netty 之 Reactor 模式中扮演的角色進行介紹。

3.EventLoopGroup / EventLoop

當系統在運行過程中,如果頻繁的進行線程上下文切換,會帶來額外的性能損耗。

多線程併發執行某個業務流程,業務開發者還需要時刻對線程安全保持警惕,哪些數據可能會被併發修改,如何保護?這不僅降低了開發效率,也會帶來額外的性能損耗。

爲了解決上述問題,Netty 採用了串行化設計理念

從消息的讀取、編碼以及後續 Handler 的執行,始終都由 IO 線程 EventLoop 負責,這就意味着整個流程不會進行線程上下文的切換,數據也不會面臨被併發修改的風險。這也解釋了爲什麼 Netty 線程模型去掉了 Reactor 主從模型中線程池。

EventLoopGroup 是一組 EventLoop 的抽象,EventLoopGroup 提供 next 接口,可以從一組 EventLoop 裏面按照一定規則獲取其中一個 EventLoop 來處理任務

對於 EventLoopGroup 這裏需要了解的是在 Netty 中,在 Netty 服務器編程中我們需要 BossEventLoopGroup 和 WorkerEventLoopGroup 兩個 EventLoopGroup 來進行工作。

通常一個服務端口即一個 ServerSocketChannel 對應一個 Selector 和一個 EventLoop 線程,也就是說 BossEventLoopGroup 的線程數參數爲 1。

BossEventLoop 負責接收客戶端的連接並將 SocketChannel 交給 WorkerEventLoopGroup 來進行 IO 處理。

EventLoop 的實現充當 Reactor 模式中的分發(Dispatcher)的角色。

4.ChannelPipeline

ChannelPipeline 其實是擔任着 Reactor 模式中的請求處理器這個角色。

ChannelPipeline 的默認實現是 DefaultChannelPipeline,DefaultChannelPipeline 本身維護着一個用戶不可見的 tail 和 head 的 ChannelHandler,他們分別位於鏈表隊列的頭部和尾部。tail 在更上層的部分,而 head 在靠近網絡層的方向。

在 Netty 中關於 ChannelHandler 有兩個重要的接口,ChannelInBoundHandler 和 ChannelOutBoundHandler。

inbound 可以理解爲網絡數據從外部流向系統內部,而 outbound 可以理解爲網絡數據從系統內部流向系統外部。

用戶實現的 ChannelHandler 可以根據需要實現其中一個或多個接口,將其放入 Pipeline 中的鏈表隊列中,ChannelPipeline 會根據不同的 IO 事件類型來找到相應的 Handler 來處理

同時鏈表隊列是責任鏈模式的一種變種,自上而下或自下而上所有滿足事件關聯的 Handler 都會對事件進行處理。

ChannelInBoundHandler 對從客戶端發往服務器的報文進行處理,一般用來執行半包 / 粘包,解碼,讀取數據,業務處理等;

ChannelOutBoundHandler 對從服務器發往客戶端的報文進行處理,一般用來進行編碼,發送報文到客戶端。

下圖是對 ChannelPipeline 執行過程的說明:

圖片

5.Buffer

Netty 提供的經過擴展的 Buffer 相對 NIO 中的有個許多優勢,作爲數據存取非常重要的一塊,我們來看看 Netty 中的 Buffer 有什麼特點。

1.ByteBuf 讀寫指針

  1. 零拷貝

  1. 引用計數與池化技術

6、總結

Netty 其實本質上就是 Reactor 模式的實現,Selector 作爲多路複用器,EventLoop 作爲轉發器,Pipeline 作爲事件處理器。

但是和一般的 Reactor 不同的是,Netty 使用串行化實現,並在 Pipeline 中使用了責任鏈模式。

Netty 中的 buffer 相對有 NIO 中的 buffer 又做了一些優化,大大提高了性能。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/X2uF9Nt-N6SiobfCStkruA