1. 程式人生 > >Netty耗時的業務邏輯應該寫在哪兒,有什麼注意事項?

Netty耗時的業務邏輯應該寫在哪兒,有什麼注意事項?

 

 

 

 

 

更多技術分享可關注我

前言 

Netty以高效能著稱,但是在實際使用中,不可避免會遇到耗時的業務邏輯,那麼這些耗時操作應該寫在哪兒呢,有什麼注意的坑嗎?本篇文章將一一總結。原文:​Netty耗時的業務邏輯應該寫在哪兒,有什麼注意事項?

Netty執行緒排程模型回顧

這部分內容前面都有總結,很簡單,只要心中有一個影象就能hold住——對於Netty來說,它的每個NIO執行緒都對應一個轉動起來的“輪盤”,即I/O事件監聽+I/O事件分類處理+非同步任務處理,三件事組成一個“輪盤”迴圈往復的轉動,直到被優雅停機或者異常中斷。。。大概結構如下:

具體細節和原始碼的分析參考:

Netty的執行緒排程模型分析(5)

Netty的執行緒排程模型分析(6)

Netty的執行緒排程模型分析(7)

Netty的執行緒排程模型分析(8)

Netty的執行緒排程模型分析(9)

本文不再贅述。

 

Netty的NIO執行緒常見的阻塞場景

知道一個大前提:Netty的ChannelHandler是業務程式碼和Netty框架交匯的地方(關於pipeline機制的細節後續專題分析,先知道即可),ChannelHandler裡的業務邏輯,正常來說是由NioEventLoop(NIO)執行緒序列執行,以Netty服務端舉例,在服務端接收到新訊息後,第一步要做的往往是用解碼的handler解碼訊息的位元組序列,位元組序列解碼後就變為了訊息物件,第二步將訊息物件丟給後續的業務handler處理,此時如果某個業務handler的流程非常耗時,比如需要查詢資料庫,那麼為了避免I/O執行緒(也就是Netty的NIO執行緒)被長時間佔用,需要使用額外的非I/O執行緒池來執行這些耗時的業務邏輯,這也是基本操作。

下面看下NIO執行緒常見的阻塞情況,一共兩大類:

  • 無意識:在ChannelHandler中編寫了可能導致NIO執行緒阻塞的程式碼,但是使用者沒有意識到,包括但不限於查詢各種資料儲存器的操作、第三方服務的遠端呼叫、中介軟體服務的呼叫、等待鎖等

  • 有意識:使用者知道有耗時邏輯需要額外處理,但是在處理過程中翻車了,比如主動切換耗時邏輯到業務執行緒池或者業務的訊息佇列做處理時發生阻塞,最典型的有對方是阻塞佇列,鎖競爭激烈導致耗時,或者投遞非同步任務給訊息佇列時異機房的網路耗時,或者任務佇列滿了導致等待,等等

JDK的執行緒池還是Netty的非I/O執行緒池?

如上一節的分析,不論是哪類原因,都需要使用非I/O執行緒池處理耗時的業務邏輯,這個操作有兩個注意的點,第一個點是需要確定使用什麼樣的業務執行緒池,第二個點是這個執行緒池應該用在哪兒?

比如下面這個Netty執行緒池使用的架構圖,熟悉Netty執行緒排程模型的人一看就懂,但是具體到非業務執行緒池的使用細節可能一部分人就不知道了:

如上圖,既然知道了應該將耗時的業務邏輯封裝在額外的業務執行緒池中執行,那麼是使用JDK的原生執行緒池,還是用其它的自定義執行緒池,比如Netty的執行緒池呢?

 

可以通過看Netty的ChannelPipeline原始碼來找到答案,如下ChannelPipeline介面的註釋寫的很明白:

即Netty建議使用它自身提供的業務執行緒池來驅動非I/O的耗時業務邏輯,如果業務邏輯執行時間很短或者是完全非同步的,那麼不需要使用額外的非I/O執行緒池。而且具體用法是Netty在新增handler時,在ChannelPipeline介面提供了一個過載的addLast方法,專用於為對應handler新增Netty業務執行緒池,如下:

其最終的內部實現如下:

提交的業務執行緒池——group物件,會被包裹進Netty的pipeline的新節點中,最終會賦值給該handler節點的父類的執行緒池executor物件,這樣後續該handler被執行時,會將執行的任務提交到指定的業務執行緒池——group執行。如下是pipeline的新節點的資料結構——AbstractChannelHandloerContext:

關於Netty的handler新增機制以及pipeline機制後續分析,暫時看不懂沒關係,先簡單瞭解。

 

直接看demo,重點是兩個紅框的程式碼:

I/O執行緒池是Nio開頭的group,非I/O執行緒池使用DefaultEventExecutorGroup這個Netty預設實現的執行緒池。在看第二個紅框處,ChannelInitializer內部類裡BusinessHandler這個入站處理器使用DefaultEventExecutorGroup執行,該處理器在channelRead事件方法裡模擬一個耗時邏輯,如下休眠3s模擬查詢大量資料:

回到demo:

EchoServerHandler也是入站處理器,且被新增在pipeline的最後,那麼進入伺服器的位元組序列會先進入BusinessHandler,再進入EchoServerHandler,下面是測試結果:

看紅框發現BusinessHandler被非I/O執行緒驅動,EchoServerHandler被NIO執行緒驅動,它的執行不受耗時業務的影響。且BusinessHandler的channelRead方法內,會將該channelRead事件繼續傳播出去,因為呼叫了父類的channelRead方法,如下:

這樣會執行到下一個入站handler——EchoServerHandler的channelRead方法。

非同步的執行結果怎麼回到Netty的NIO執行緒?

我看不少初學者會搞不明白這裡,即將非同步任務丟給了服務端外部的非I/O執行緒池執行,那外一客戶端需要非同步任務的計算結果,這個計算的結果怎麼回到Netty的NIO執行緒呢,即他們懷疑或者說不理解這個非同步結果是怎麼被Netty發出去給客戶端的呢?

可以繼續看第3節的demo的執行結果:

發現BusinessHandler確實是被非I/O執行緒驅動的,即日誌列印的執行緒名是default開頭的,而EchoServerHandler又確實是被NIO執行緒驅動的,即日誌列印的執行緒是nio開頭的,它的執行不受耗時業務的影響。這底層流轉到底是怎麼回事呢,且看分解。如下是demo裡,BusinessHandler的channelRead方法:

該方法是一個回撥的使用者事件,當Channel裡有資料可讀時,Netty會主動呼叫它,這種機制後續專題總結,這裡知道結論即可。注意紅線處前面也提到了——會將該channelRead事件繼續傳播給下一個handler節點,即執行到下一個入站處理器——EchoServerHandler的channelRead方法。而在pipeline上傳播這個事件時,Netty會對其驅動的傳播過程做一個判斷。看如下的invokeChannelRead方法原始碼:其中引數next是入站節點EchoServerHandler,其executor是NIO執行緒,核心程式碼如下紅框處——會做一個判斷:

如果當前執行的執行緒是Netty的NIO執行緒(就是該Channel繫結的那個NIO執行緒,即executor,暫時不理解也沒關係,知道結論,後續專題分析),那麼就直接驅動,如果不是NIO執行緒,那麼會將該流程封裝成一個新的task扔到NIO執行緒的MPSCQ,排隊等待被NIO執行緒處理,這裡關於MPSCQ可以參考:Netty的執行緒排程模型分析(9)。因此將耗時的業務邏輯放到非NIO執行緒池處理,也不會影響Netty的I/O排程,仍然能通過NIO執行緒向客戶端返回結果。

JDK的執行緒池拒絕策略的坑可能導致阻塞

使用過執行緒池的都知道,如果業務邏輯處理慢,那麼會導致執行緒池的阻塞佇列積壓任務,當積壓任務達到容量上限,JDK有對應的處理策略,一般有如下幾個已經提供的拒絕策略:

ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由呼叫執行緒處理該任務

JDK執行緒池預設是AbortPolicy,即主動丟擲RejectedExecutionException

回到Netty,如果使用其非I/O執行緒池不當,可能造成NIO執行緒阻塞,比如業務上有人會設定執行緒池滿的拒絕策略為CallerRunsPolicy 策略,這導致會由呼叫方的執行緒——NioEventLoop執行緒執行業務邏輯,最終導致NioEventLoop執行緒可能被長時間阻塞,在服務端就是無法及時的讀取新的請求訊息。

實際使用Netty時,一定注意這個坑。即當提交的任務的阻塞佇列滿時,再向佇列加入新的任務,千萬不能阻塞NIO執行緒,要麼丟棄當前任務,或者使用流控並向業務方和運維人員報警的方式規避這個問題,比如及時的動態擴容,或者提高演算法能力,提升機器效能等。

使用Netty非I/O執行緒池的正確姿勢

前面其實分析過Netty的EventExecutorGroup執行緒池,可以參考:Netty的執行緒排程模型分析(10)——《Netty有幾類執行緒池,它們的區別,以及和JDK執行緒池區別?》,它也是類似NIO的執行緒池機制,只不過它沒有繫結I/O多路複用器,它和Channel的繫結關係和NIO執行緒池一樣,也是來一個新連線,就用執行緒選擇器選擇一個執行緒與之繫結,後續該連線上的所有非I/O任務,都在這一個執行緒中序列執行,此時並不能發揮EventExecutorGroup的作用,即使初始值設定100個執行緒也無濟於事。

兩句話:

1、如果所有客戶端的併發連線數小於業務執行緒數,那麼建議將請求訊息封裝成任務投遞到後端普通業務執行緒池執行即可,ChannelHandler不需要處理複雜業務邏輯,也不需要再繫結EventExecutorGroup

2、如果所有客戶端的併發連線數大於等於業務需要配置的執行緒數,那麼可以為業務ChannelHandler繫結EventExecutorGroup——使用addLast的方法

後記

dashuai的部落格是終身學習踐行者,大廠程式設計師,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於網際網路行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!

 

相關推薦

Netty耗時業務邏輯應該哪兒什麼注意事項

          更多技術分享可關注我 前言  Netty以高效能著稱,但是在實際使用中,不可避免會遇到耗時的業務邏輯,那麼這些耗時操作應該寫在哪兒呢,有什麼注意的坑嗎?本篇文章將一一總結。原文:​Netty耗時的業務邏輯應該寫在哪兒,有什麼

面對數據丟失、數據錯誤、業務邏輯發生變化時可以這麽解決。

誤操作 data 數據丟失 系統 輸入 根據 圖片 src 了解 根據福克斯新聞在20日的報道,美國田納西州一名14歲男孩Jackson成功在家中打造出核融合實驗的小型聚變反應器,成功結合2個氘原子、釋出一顆中子。 關鍵是,Jackson所需的零件是從網上購買,或自己改裝的

專業代文章需要注意什麼

  我們做任何事情都離不開語言表達,從小學三年級開始我們就學著寫作文。慢慢的我們通過年齡的增長和經驗的積累,寫作能力也隨之加強。所謂代寫文章指的就是,通過自己的語言表達,把內心的所想所感表現出來的一種文字表達。   文章的文體可以分為記敘文、說明文和議論文。每一種文體都有不同的表達方式。所以在代寫文

重灌系統後重新安裝ORACLE加環境變數配置、客戶端PL/SQL的安裝過程注意事項(避免再次踩坑)

(1)首先了解什麼是OERACLE及Oracle與PL/SQL是什麼關係: ORACLE是資料庫,有客戶端和伺服器; PLSQL Developer只是第三方工具,服務於ORACLE,類似的工具還有Toad,sqlplus,sql developer等等; 安裝PLSQL Developer

JUnit4測試類使用以及注意事項

 專案的目錄配置檔案:  建立測試類: import org.activiti.engine.*; import org.activiti.engine.identity.User; import org.activiti.engine.impl.persiste

【異常帖】--- 大資料出現的所有異常錯誤注意事項整理---持續更新.....

一、Ubuntu --> 修改主機名稱之後,要特別主要修改主機和ip的對映(否則會造成java.net.UnknownHostException: 主機名: 主機名的異常,mkdir: Call From java.net.UnknownHostException: s100: s100: u

關於使用echarts心得及其注意事項

第一步匯入jquery、echarts的包 程式碼如下: <script src="static/scripts/jquery/echarts.js"></script> <script src="static/scripts/jquer

Mysql讀分離架構及注意事項

下面實現一個簡單的Mysql雙主多從的架構,如下圖所示: 這個架構首先考慮到的是做故障轉移,提高可用性,保證整個叢集的穩定性。 另外我們要注意以下事項: 1.當主庫發生故障時,能自動切換到備用主庫,並且要注意主庫恢復後如何繼續同步; 2.從庫應該連線主庫的一個虛擬IP,這樣就可以做到主庫

RF用例編寫注意事項

知識點: 1.介面文件裡面classlist的[{“id”:230},{“id”:231}]是json格式的字串,最終rf中輸入傳參的時候是可以這樣[{“id”:230},{“id”:231}]寫的,但是這樣寫對QA編寫用例的人來說,這樣寫有點複雜,最好就是將id傳給介面就行,

便於計算的rem使用方法以及注意事項

一、相容性。 目前,IE9+,Firefox、Chrome、Safari、Opera 的主流版本都支援了rem(大膽用吧,目前幾乎所有手機瀏覽器都支援rem) 二、什麼是rem。 rem是相對於根元素html字型大小來計算的,即( 1rem = html字型大小 )

java web開發注意事項

匯出的jar如何讓springMVC能掃描到? 開發中發現,打包成jar然後再匯入到專案中springMVC掃描不到,於是查詢資料終於解決了這個問題。 怎麼匯出jar? 將jar配置到專案? 新建一個資料夾用於存放jar的 然後選擇要設定的jar,點選f

delphi在64位系統下登錄檔注意事項

HKEY_LOCAL_MACHINE寫這個主鍵下的項,在64位系統下可能會重定向,所以構造時要加KEY_WOW64_64KEY reg := TRegistry.Create(KEY_WRITE or KEY_READ or KEY_WOW64_64KEY); r

上傳圖片時注意事項

/** * 後臺上傳圖片 * type 1 為小程式圖片 */ public function actionAdd(){ if($_FILES && $_FILES["image"]['name'

idea建立ssm專案、增刪改查操作以及注意事項

專案技術工具: jsp + springmvc + spring + mybatis +mysql5.7 maven 3.3.9 tomcat 8.5 + jdk 1.8 idea建立專案: 或者 或者 next然後

jmeter測試webservice完整教程附帶注意事項

一、Jdk下載安裝環境配置 2、安裝jdk,下載完成後,雙擊安裝 3.配置jdk環境變數 右鍵計算機屬性->高階系統設定->系統屬性->高階->環境變數->新增系統變數: 變數名:【JAVA_HOME】 變數值:【D:\Program Fil

C++ STL使用以及注意事項

C++ STL的vector容器在clear()之後不會釋放記憶體,需要 swap(empty vector),這是有意為之(C++11 裡增加了 shrink_to_fit() 函式)。不要記成了所有STL容器都需要swap(empty one)來釋放記憶體。事實上其他容器(map/set/list/deq

APP介面需要注意事項

1.首先如果你是後入公司的,先跟app的確認技術,是使用wcf。還是webservice,或者是其他的先進技術。反正要是他們覺得不好,你寫的再好反正還是讓你返工,so第一步除了需求也的先確定技術 2.

Excel隨機生成批量日期以及注意事項

這個是WPS裡寫的一個函式,用來隨機生成日期。首先E1和E2是兩個日期端點,右鍵把單元格格式先設定成“日期”中的“xxxx年xx月xx日 xx:xx”,然後E3=E1-E2算出它們的距離。 在E4裡面,寫如圖的函式=$E$1-RANBETWEEN(0,$E$

微信開發者工具初始化專案時進去報錯:小程式重啟耗時過久請確認業務邏輯中是否複雜運算或者死迴圈

              如圖上,為錯誤原因。   之前用開發者工具用的好好地,前些日子又版本更新,升級了一下,然後開啟專案就出現上面的問題。當時以為電腦出啥問題了,也沒當回事。今天再次開啟開發者工具,發現還是出現上面的問題。一臉懵,新建專案,啥都沒做竟然出問題。網上一查同樣的問題一大堆。

GoSqlGo 首個正式版釋出在前端 SQL 和業務邏輯

   GoSqlGo簡介 | Description GoSqlGo是一個運行於後端的服務端工具,它的最大特點就是在開發期動態編譯客戶端Java程式碼,所有SQL和Java程式碼都可以在前端Html頁面完成,業務開發可以不再依賴後端程式設計師了。  更新內容:&nbs