面試官:Netty的線程模型,可不只是主從多Reactor這麼簡單!

筆者看來Netty的內核主要包括如下圖三個部分:


其各個核心模塊主要的職責如下:

  • 內存管理
    主要提高高效的內存管理,包含內存分配,內存回收。

  • 網通通道
    複製網絡通信,例如實現對NIO、OIO等底層JAVA API 的封裝,簡化網絡編程模型。

  • 線程模型

    提供高效的線程協作模型。

大家不妨回想一下在以往的面試的過程中,面試官通常會問:Netty 的線程模型是什麼?

主從多 Reactor 模型,相信大家都能脫口而出,然後呢?就沒有然後了?

線程模型在網絡通信中主要解決什麼樣的問題?在 Netty 中又是如何解決的,Netty 的線程模型爲什麼如此高效?請容我慢慢道來。

溫馨提示:爲了保證文章觀點的嚴謹性,將探究領域鎖定在:Netty NIO 相關。
另外:整理了一份Java面試寶典完整版PDF,已成文檔

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模型是如何解決上面的問題呢?

  1. 連接建立(OP_ACCEPT)由 Main Reactor 線程池負責,創建NioSocketChannel後,將其轉發給SubReactor。

  2. SubReactor 線程池主要負責網絡的讀寫(從網絡中讀字節流、將字節流發送到網絡中),即註冊OP_READ、OP_WRITE,並且同一個通道會綁定一個SubReactor線程

  3. 編碼、解碼、業務執行,則具體情況具體分析

    通常編碼、解碼會放在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 的線程模型是什麼?”時,我們應該可以從容應對了。

我覺得可以從如下幾個方面進行展開。

  1. Netty 的線程模型基於主從多Reactor模型。通常由一個線程負責處理OP_ACCEPT事件,擁有 CPU 核數的兩倍的IO線程處理讀寫事件。
  2. 一個通道的IO操作會綁定在一個IO線程中,而一個IO線程可以註冊多個通道。
  3. 在一個網絡通信中通常會包含網絡數據讀寫,編碼、解碼、業務處理。默認情況下編碼、解碼等操作會在IO線程中運行,但也可以指定其他線程池。
  4. 通常業務處理會單獨開啓業務線程池,但也可以進一步細化,例如心跳包可以直接在IO線程中處理,而需要再轉發給業務線程池,避免線程切換。
  5. 在一個IO線程中所有通道的事件是串行處理的。

作者:中間件興趣圈
原文鏈接:https://blog.csdn.net/prestigeding/article/details/112405349

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章