Netty的Reactor線程組中的每個Reactor線程處理網路I/O為什麼一定要串列化,並行化不是更能提高系統吞吐量嗎?

上圖是一個Reactor線程模型,基於select(),epoll(),Reactor線程將I/O調用請求和I/O操作分離開,理論上一個Reactor線程能處理N個網路I/O操作,但是當並發量很大的時候,一個Reactor線程就會出現響應緩慢,連接超時等問題。

============================================================

那麼一個Reactor線程模型應付不了大並發的網路請求,我們就可以更進一步選擇Reactor線程組,一下是netty的主從Reactor線程組模型:

Reactor線程組實現了多個Reactor線程並行處理網路I/O,但是一個Reactor線程在同一時刻只能處理一個網路I/O(netty中是這麼選擇的)

有問題要請教了,Reactor線程能將網路I/O調用請求和I/O操作分離開來,那麼我們完全可以將I/O操作封裝成一個TASK交給自定義的線程池去執行啊,這樣的話不就加大了程序的並行化,提高了系統的吞吐量嗎?(雖然它帶來了線程上下文切換的開銷)

-----------將Reactor線程組中的單個Reactor線程模型該成一下這樣:


我也有題主相同的疑問。

借用Doug Lea大叔的一張圖

網路伺服器的基本功能就是建立連接,讀取請求,解析請求,調用處理過程,編碼結果,發送結果。

下面部分截圖來源於網路

Netty是典型的Reactor模型結構,下面說明一下Reactor模型的經典編程結構

單線程,即只有一個selector來接受建立連接請求,讀取數據,處理請求,返回數據。

採用了多線程,雖然仍舊是單個selector線程,但是請求的處理大頭交給了線程池非同步執行。

事實上,大部分網路程序為了更高的性能,都採用了多個Selector線程,即Reactor線程組的模型,Netty也不例外,採用了多個Selector。

------------------------------------------------------------------------------------------------------------------------

這張圖近似於Netty的Reactor模型,唯一不同的是Netty並沒有ThreadPool來非同步的處理請求,而是串列的處理請求,

合適了。

-------------------------------------------------------------------------------------------------------------

我司(遊戲伺服器)採用的就是第二種Reactor模型,默認啟用了4個Selector。

同時將請求的解析和請求的處理獨立出了兩個線程池(不過現在看來每個線程池都是驚人的100個線程有點不安)selector線程將數據獲取之後(自然是byte[]類型),丟給請求解析線程池去分發,分發到請求處理線程池當中。

附帶說一句,我上家單位(也是遊戲公司)使用Netty做網路通信框架,就是使用Reactor同步處理請求和IO。

雖然都使用過兩種模型,但是畢竟遊戲類型不同,資料庫完全不同(一個mysql 一個自研nosql 還是java封裝的nosql,nio也是自己封裝的)所以很難比較

例外採用非同步的處理時在遊戲伺服器場景中可能出現:兩個請求同時到達被處理,這是不能被允許的,因此需要確保處理完這個人第一個請求之後再處理下一個請求。

-----------------------------------------------------------------------------------------------------------------

由於採用了非同步並行的處理方式,所以在返回數據的時候需要指明是哪個Channel的數據,需要保存上下文。

--------------------------------

正如上面各位大牛所說,線程切換和保存上下文的開銷也是很大的,但是不足以使內存爆掉 @Intopass

具體怎麼選取應用場景還請各位並發編程大牛解讀。

------------------------------------------

目前看來,只是使用netty這種同步的處理方式是不妥的,使用mysql的資料庫很有可能造成邏輯處理的耗時操作。

下面的操作是可行的。

Netty是事件驅動的,可以通過ChannelHandler鏈來控制執行流向。因為ChannelHandler鏈的執行過程是在subReactor中同步的,所以如果業務處理handler耗時長,將嚴重影響可支持的並發數。

這種模型適合這種模型適合於像 Memcache 這 樣的應用場景, 但 對需要操作資料庫或者和其他模塊阻塞交互的系統就不是很 合適。

但是Netty 的可擴展性非常好, 而像 ChannelHandler 線程池化的需要, 可以 通過在ChannelPipeline 中添加 Netty 內置的 ChannelHandler 實現類 –ExecutionHandler 實現, 對使用者來說只是 添加一行代碼而已。

對於 ExecutionHandler 需要的線程池模型, Netty 提供了兩種可 選:

1) MemoryAwareThreadPoolExecutor 可控制 Executor 中待處理任務的上限( 超過 上限時, 後續進來的任務將被阻 塞) , 並可控制單個 Channel 待處理任務的上 限;

2) OrderedMemoryAwareThreadPoolExecutor 是 MemoryAwareThreadPoolExecutor 的子類, 它還可以保證同一 Channel 中處 理的事件流的順序性, 這主要是控制事件在非同步處 理模式下可能出現的錯誤的 事件順序, 但它並不保證同一 Channel 中的事件都在一個線程中執行( 通常也沒 必要) 。 一般來 說, OrderedMemoryAwareThreadPoolExecutor 是個很不錯的選 , 也可以 DIY 一個。

下面的參考資料

Netty系列之Netty線程模型

2.4. Netty線程開發最佳實踐

2.4.1. 時間可控的簡單業務直接在IO線程上處理

如果業務非常簡單,執行時間非常短,不需要與外部網元交互、訪問資料庫和磁碟,不需要等待其它資源,則建議直接在業務ChannelHandler中執行,不需要再啟業務的線程或者線程池。避免線程上下文切換,也不存在線程並發問題。


2.4.2. 複雜和時間不可控業務建議投遞到後端業務線程池統一處理

對於此類業務,不建議直接在業務ChannelHandler中啟動線程或者線程池處理,建議將不同的業務統一封裝成Task,統一投遞到後端的業務線程池中進行處理。

過多的業務ChannelHandler會帶來開發效率和可維護性問題,不要把Netty當作業務容器,對於大多數複雜的業務產品,仍然需要集成或者開發自己的業務容器,做好和Netty的架構分層。

這個問題也可以參考

Netty中,耗時的業務邏輯代碼應該寫在哪? - Java


使用NIO就意味著在處理IO過程中不會發生無意義的阻塞 也就不需要為了"阻塞需求"啟動超過實際處理能力的線程數 這樣減少了無意義的切換

這裡的串列不是指等待一個"業務上的請求"完成後再繼續處理下一個請求 而是:

選擇器返回可讀套接字(1) -&> 接受數據(2) -&> 按照業務需求encode -&> 是否完成 -&> 是 -&> 委託業務處理線程 -&> 退回到步驟1
-&> 否 -&> 指定套接字是否有剩餘的內容 -&> 否 -&> 退回到步驟1
是 -&> 返回到步驟2


主要是降低複雜度。

您可以購買我翻譯的《Netty實戰》這本書。

如何評價《Netty實戰》這本書? 知乎《Netty IN ACTION》中文版《Netty實戰》翻譯手記--不負好時光

京東鏈接(現貨發售):《Netty實戰》([美]諾曼·毛瑞爾(Norman Maurer),馬文·艾倫·沃爾夫泰爾(Marvin Allen Wolfthal))


通俗來講,每個請求用一個線程來處理,一是可以保證位元組序列傳過來進行解碼,分包,還有讀取等的順序性

二是避免線程間資源競爭,線程切換的開銷

這就提高了系統性能


現在伺服器一般是多核,伺服器應該要利用多核,多核的時候,線程數核數接近能最大化性能,不然線程切換有損耗。所以伺服器設計一般是一個主線程accept得到fd之後分發到其他線程,其他線程做epoll處理I/O, 這些處理I/O的線程都不能阻塞太舊,不然會影響其他連接的請求響應速度。"但是當並發量很大的時候,一個Reactor線程就會出現響應緩慢,連接超時等問題。"...可能是你在handler裡面阻塞了太久。


應該是同一個TCP連接的I/O處理只能串列化。畢竟TCP的緩衝區的讀寫都是不能並發的。


那個串列指是的當前請求:解碼-&>業務 處理

當解碼完了再提交給線程處理有上下文開銷的,如果沒有指定線程組當前線程繼續執行業務處理

目前是減少上下文切換開銷


多線程處理io可能會導致信令亂序,造成業務失敗。


同步問題

並行只適用於上下文無關重複性高的操作

否則各種鎖性能反而下降


推薦閱讀:

為什麼nio效率會比bio高?

TAG:Java虛擬機JVM | Java編程 | epoll | 網路編程 | Netty |