筆者看來Netty的核心主要包括如下圖三個部分:
其各個核心模組主要的職責如下:
-
記憶體管理
主要提高高效的記憶體管理,包含記憶體分配,記憶體回收。 -
網通通道
複製網路通訊,例如實現對NIO、OIO等底層JAVA API 的封裝,簡化網路程式設計模型。 -
執行緒模型
提供高效的執行緒協作模型。
大家不妨回想一下在以往的面試的過程中,面試官通常會問:Netty 的執行緒模型是什麼?
主從多 Reactor 模型,相信大家都能脫口而出,然後呢?就沒有然後了?
執行緒模型在網路通訊中主要解決什麼樣的問題?在 Netty 中又是如何解決的,Netty 的執行緒模型為什麼如此高效?請容我慢慢道來。
溫馨提示:為了保證文章觀點的嚴謹性,將探究領域鎖定在:Netty NIO 相關。
1、主從多 Reactor 模型
主從多 Reactor 模型是業界一種非常經典的執行緒程式設計模型,其原理圖如下所示:
我們首先簡單介紹一下上圖中涉及的幾個重要角色:
-
Acceptor
請求接收者,在實踐時其職責類似伺服器,並不真正負責連線請求的建立,而只將其請求委託 Main Reactor 執行緒池來實現,起到一個轉發的作用。
-
Main Reactor
主 Reactor 執行緒組,主要負責連線事件,並將IO讀寫請求轉發到 SubReactor 執行緒池。當然在一些需要對客戶端進行許可權控制等場景下,許可權校驗的職責可以放到 Main Reactor 執行緒池,即 Main Reactor 也可以註冊通道的讀寫事件,讀取客戶端許可權校驗相關的資料包,執行許可權驗證,許可權驗證通過後再將2通道註冊到IO執行緒。 -
Sub Reactor
Main Reactor 通常監聽客戶端連線後會將通道的讀寫轉發到 Sub Reactor 執行緒池中一個執行緒(負載均衡),負責資料的讀寫。在 NIO 中 通常註冊通道的讀(OP_READ)、寫事件(OP_WRITE)。
為了更加深刻的理解主從 Reactor 模型,我們來看一下網路通訊一般會包含哪些關鍵動作:
一個網路互動通常的幾個步驟如下:
- 服務端啟動,並在特定埠上監聽,例如 web 應用的 80埠。
- 客戶端發起TCP的三次握手,與服務端建立連線,這裡以 NIO 為例,連線成功建立後會建立NioSocketChannel物件。
- 服務端通過 NioSocketChannel 從網路卡中讀取資料。
- 服務端根據通訊協議從二進位制流中解碼出一個個請求。
- 根據請求,執行對應的業務操作,例如 Dubbo 服務端接受一個查詢使用者ID為1的使用者資訊。
- 將業務執行結果返回到客戶端,通常涉及到協議編碼、壓縮等。
執行緒模型需要解決的問題:連線監聽、網路讀寫、編碼、解碼、業務執行這些操作步驟如何運用多執行緒程式設計,提升效能。
主從多Reactor模型是如何解決上面的問題呢?
-
連線建立(OP_ACCEPT)由 Main Reactor 執行緒池負責,建立NioSocketChannel後,將其轉發給SubReactor。
-
SubReactor 執行緒池主要負責網路的讀寫(從網路中讀位元組流、將位元組流傳送到網路中),即註冊OP_READ、OP_WRITE,並且同一個通道會繫結一個SubReactor執行緒。
-
編碼、解碼、業務執行,則具體情況具體分析
通常編碼、解碼會放在IO執行緒中執行,而業務邏輯的執行通常會採用額外的執行緒池,但不是絕對的,一個好的框架通常會使用引數來進行定製化選擇,例如 ping、pong 這種心跳包,直接在 IO 執行緒中執行,無需再轉發到業務執行緒池,避免執行緒切換開銷。
溫馨提示:在網路程式設計中,通常將用於網路讀寫的執行緒稱為IO執行緒。
2、Netty 的執行緒模型
Netty的執行緒模型是基於主從多Reactor模型。
Netty 中網路的連線事件(OP_ACCEPT)由Main Reactor 執行緒組實現,即 Boss Group,通常只需設定一個執行緒。
網路的讀寫操作由 Work Group ( Sub Reactor) 執行緒組來實現,執行緒的個數預設為 2 * CPU Core,一個 Channel 繫結到其中一個 Work 執行緒,一個 Work 執行緒中可以繫結多個 Channel。
在 Netty 中編碼、解碼等操作會被封裝成一個一個事件處理器(ChannelHandler),那這些 Handler 是在IO執行緒池中執行?
預設情況下ChannelHandler 是在 IO 執行緒中執行,那如何改變預設行為呢?其關鍵程式碼如下:
關鍵點:在將事件處理器新增到事件鏈時可以指定在哪個執行緒池中執行,如果不指定則為IO執行緒中執行。
面試官:通常業務操作會專門開闢一個執行緒池,那業務處理完成之後,如何將響應結果通過 IO 執行緒寫入到網路卡中呢?
業務執行緒呼叫 Channel 物件的 write 方法並不會立即寫入網路,只是將資料放入一個待寫入佇列(快取區),然後IO執行緒每次執行事件選擇後,會從待寫入快取區中獲取寫入任務,將資料真正寫入到網路中,資料到達網路卡之前會經過一系列的 Channel Handler(Netty事件傳播機制),最終寫入網路卡。
最後再來介紹一下 Netty 中 IO 執行緒的大體工作流程。
IO執行緒處理的關鍵點:
- 每一IO執行緒在執行上述操作時是序列執行的,即註冊在一個 Selector(事件選擇器)中的所有通道,同一時間只有一個通道的事件被處理。這也是為什麼NIO應對大檔案傳輸時不具備優勢的根本原因。
- IO 執行緒在處理完所有就緒事件後,還會從任務佇列(Task Queue)獲取任務,例如上文中提到的業務執行緒在執行完業務後需要將返回結果寫入網路,Netty 中所有的網路讀寫操作只能在IO執行緒中真正獲得執行,故業務執行緒需要將帶寫入的響應結果封裝成 Task,放入到 IO 執行緒任務佇列中。
3、總結
回到到主題,如果我們在面試過程中碰到面試官提問“Netty 的執行緒模型是什麼?”時,我們應該可以從容應對了。
我覺得可以從如下幾個方面進行展開。
- Netty 的執行緒模型基於主從多Reactor模型。通常由一個執行緒負責處理OP_ACCEPT事件,擁有 CPU 核數的兩倍的IO執行緒處理讀寫事件。
- 一個通道的IO操作會繫結在一個IO執行緒中,而一個IO執行緒可以註冊多個通道。
- 在一個網路通訊中通常會包含網路資料讀寫,編碼、解碼、業務處理。預設情況下編碼、解碼等操作會在IO執行緒中執行,但也可以指定其他執行緒池。
- 通常業務處理會單獨開啟業務執行緒池,但也可以進一步細化,例如心跳包可以直接在IO執行緒中處理,而需要再轉發給業務執行緒池,避免執行緒切換。
- 在一個IO執行緒中所有通道的事件是序列處理的。
好了,本文就介紹到這裡了,您的點贊與轉發是對我持續輸出高質量文章最大的鼓勵。更多文章推薦關注我的公眾號[中介軟體興趣圈]