作者:李林峯;
來源:Netty之家

問題背景:

某互聯網同學諮詢一個Netty使用問題:最近在研究公司內部的RPC框架,發現底層通信框架使用的是Netty,而且Netty的I/O線程與處理業務的線程分離。具體如下:

1、負責服務端監聽的是Accept NioEventLoopGroup線程組

2、負責鏈路讀寫操作的是Work NioEventLoopGroup線程組

3、消息解碼完成之後,投遞到後端的一個業務線程池中處理,線程池使用的是JDK自帶的線程池

該同學的疑問:爲什麼業務的處理不能放到Work NioEventLoopGroup中?

1、如果業務線程處理比較慢,即便I/O線程處理再快,業務端到端響應還是不會縮短

2、I/O線程到業務線程存在線程上下文切換,增加了額外的開銷

想法:

構造一個線程數較大(例如1024)的NioEventLoopGroup,同時處理鏈路的讀寫和業務處理。即業務處理和消息讀寫統一使用Netty的I/O線程池(實質自定義的線程組)。


問題答覆

Netty I/O線程和業務處理線程分離原因:

1、充分利用多核的並行處理能力:I/O線程和業務線程分離,雙方可以並行的處理網絡I/O和業務邏輯,充分利用多核的並行計算能力,提升性能。

2、故障隔離:後端的業務線程池處理各種類型的業務消息,有些是I/O密集型、有些是CPU密集型、有些是純內存計算型,不同的業務處理時延,以及發生故障的概率都是不同的。如果把業務線程和I/O線程合併,就會存在如下問題:

1)某類業務處理較慢,阻塞I/O線程,導致其它處理較快的業務消息的響應無法及時發送出去。

2)即便是同類業務,如果使用同一個I/O線程同時處理業務邏輯和I/O讀寫,如果請求消息的業務邏輯處理較慢,同樣會導致響應消息無法及時發送出去。

3、可維護性:I/O線程和業務線程分離之後,雙方職責單一,有利於代碼維護和問題定位。如果合設在一起,當RPC調用時延增大之後,到底是網絡問題、還是I/O線程問題、還是業務邏輯問題導致的時延大,糾纏在一起,問題定位難度非常大。例如業務線程中訪問緩存或者數據庫偶爾時延增大,就會導致I/O線程被阻塞,時延出現毛刺,這些時延毛刺的定位,難度非常大。

4、資源代價:NioEventLoopGroup的創建並不是廉價的,它會聚合Selector,Selector本身就會消耗句柄資源。

Netty的NioEventLoop設計理念就是通過有限的I/O線程,通過多路複用和非阻塞的方式,一個線程同時處理成百上千個鏈路,來解決傳統一連接一線程的同步阻塞模型。

因此,它的創建成本也較高,一個進程中不宜創建過多NioEventLoop。

相關代碼如下所示:

爲什麼建議 Netty 的 I/O 線程與業務線程分離


5、線程切換的代價:如果不是追求極致的性能,線程切換隻要不過於頻繁,它的代價還是可以接受的。在一個複雜的系統中,當你集成第三方SDK時,例如Redis Client,通常都包含着隱式的線程切換。一些場景下,爲了實現系統的高可用性,例如 基於Hystrix做故障隔離,同樣會存在線程切換:Hystrix基於線程做故障隔離

爲什麼建議 Netty 的 I/O 線程與業務線程分離



該問題引申的其它幾個問題


1、Netty的I/O線程 NioEventLoop是否可以處理非I/O任務?

答案是肯定的,通過它提供的接口就可以看出這點:

爲什麼建議 Netty 的 I/O 線程與業務線程分離


它的execute方法參數是Runnable,與JDK的線程池execute方法是等價的(異常處理策略存在差異)。

開了這個口子之後,就會存在風險,例如用戶爲了簡化線程處理模型,把所有的業務任務封裝成Task,丟到Nett用的I/O線程NioEventLoop中執行。爲了防止過多的業務任務阻塞I/O線程的網絡讀寫操作,NioEventLoop提供了設置I/O任務和非I/O任務的處理比例,通過合理的調整處理比例,來保證更合理的資源調度。

爲什麼建議 Netty 的 I/O 線程與業務線程分離


Netty並不反對在I/O線程中處理非I/O任務,而是需要用戶必須要避免意外的I/O線程阻塞,以及過多的佔用I/O任務調度,導致網絡I/O處理性能下降。

2、一個超大的JDK業務線程池是不合適的,原因有兩個:

1)性能問題:JDK線程池默認採用一個阻塞隊列,N個work線程的模式,隨着work線程數的增加、隊列的爭用會非常激烈,進而導致性能下降。

建議採用N組線程池,每個線程池線程數儘量少的方式增加並行處理能力,

減少鎖爭用。

爲什麼建議 Netty 的 I/O 線程與業務線程分離


2)故障隔離問題:如果後端只有一個線程池,某個服務故障將會導致整個進程不可用。採用分組處理業務服務的方式,可以降低故障的影響範圍,示例如下所示:

爲什麼建議 Netty 的 I/O 線程與業務線程分離


相关文章