​結合非同步模型,再次總結Netty多執行緒編碼最佳實踐

更多技術分享可關注我

前言

本文重點總結Netty多執行緒的一些編碼最佳實踐和注意事項,並且順便對Netty的執行緒調度模型,和非同步模型做了一個匯總。原文:​​結合非同步模型,再次總結Netty多執行緒編碼最佳實踐

Netty多執行緒編碼的最佳實踐總結

接該文:Netty的執行緒調度模型分析(10)《Netty多執行緒開發的最佳實踐有哪些?》

回憶:

1、服務端需要啟動兩個NioEventLoopGroup,其中boss(新連接接入)執行緒池大小設置為1即可,設置多了也是1個I/O執行緒在起作用,而且還浪費記憶體。

2、如果業務非常簡單,執行時間非常短,不需要與外部網元交互、比如訪問資料庫等,也不需要等待其它資源,那麼建議直接在業務ChannelHandler中執行,不需再啟動Netty的非I/O執行緒池或者使用額外的執行緒池,避免大量CPU上下文切換的開銷,而且也不存在執行緒安全問題

3、如果業務邏輯耗時較大,或者是時間不可控的業務,比如查詢資料庫,那麼建議封裝為task後,投遞到後端的非I/O執行緒池統一處理,可以使用Netty默認提供的無鎖串列化執行緒池——DefaultEventExecutorGroup,在添加handler的時候綁定即可,或者直接投遞到另外的進程中處理,比如消息隊列,最後把計算結果發送給Netty的I/O執行緒即可,如果使用了非I/O執行緒驅動耗時邏輯,那麼再傳遞結果時,Netty的I/O執行緒會判斷當前執行緒是不是I/O執行緒,如果不是,那麼Netty會自動將該邏輯封裝為task扔到MPSCQ,並讓I/O執行緒驅動,因此不用擔心業務執行緒池的結果無法返回到I/O執行緒

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

 

額外補充幾點:

1、善於使用Netty的鉤子方法:參考:Netty的非同步模型分析(5)

2、盡量不要在ChannelHandler中啟動用戶執行緒(解碼消息並派發消息到非I/O執行緒池除外)

3、解碼處理器應該被NIO執行緒調用,不要切換到用戶執行緒

4、一個EventLoop在它的整個生命周期當中都只會與唯一一個永遠不會被改變的Thread進行綁定,所有由EventLoop處理的I/O事件都將在它所關聯的那個Thread上進行處理,一個Channel在它的整個生命周期中只會註冊在一個EventLoop上,一個EventLoop在運行過程當中,會被分配給一個或者多個Channel,得出重要結論:在Netty中,Channel的實現一定是執行緒安全的,基於此,用戶可以存儲一個Channel的引用,並且在需要向遠程建點發送數據時,通過這個引用來調用Channel相應的方法,即便當時有很多執行緒都在使用它也不會出現執行緒不安全問題,而且消息一定會按順序發出去。

5、千萬理解Netty的NIO執行緒的模型,它的樣子,如果是非同步API被NIO執行緒驅動,那麼該API本質還是串列的,因為NIO執行緒是非同步串列無鎖化模型,要想實現非串列執行,必須將非同步API封裝為task,讓非I/O執行緒驅動。當然對於簡單的收發消息,大可不必這樣笨拙,大膽的讓NIO執行緒驅動即可。具體參考:Netty的writeAndFlush()非同步實現源碼分析和正確用法

6、可以使用重載的addLast方法,在向pipeline添加handler時,傳入Netty提供的非I/O執行緒池——DefaultEventExecutor,此後該handler上的事件,都是傳入的group執行緒池來執行。具體參考:Netty耗時的業務邏輯應該寫在哪兒,有什麼注意事項?

7、強烈建議使用Netty的非同步API時,都為其附加一個Netty的監聽器,監聽非同步I/O的結果,盡量少用非同步轉同步API,即NIO執行緒能不阻塞就不阻塞。

8、如果要使用非同步轉同步API,那麼必須使用非NIO執行緒驅動,否則會死鎖,具體原因見:Netty非同步API轉同步的實現原理和正確用法

個人階段性感想暨對Netty的認識總結

從18年年中接觸Netty,讀過《Netty實戰》,《Netty權威指南》,《跟著案例學Netty》,以及閃電俠的源碼分析課程,直到自己通過demo入手通讀了它的源碼,到現在已經快兩年了,期間搞過Netty的小輪子,比如仿微信聊天伺服器,HTTP長連接客戶端,簡單的分散式RPC框架,有了這些經歷才催生了這一系列筆記文章。

水平一般,能力有限,就我個人理解,對Netty的抽象如下:

 

 

理解Netty的核心就是4個影像,或者說模型也OK,之所以我現在叫它們影像,是因為將這些機制想像抽象為圖形,比較容易理解和加深印象,分別是:

 

1、執行緒調度影像

即對epoll機制,NIO三大組件(I/O多路復用器Selector+Channel+Buffer)和基本的網路編程流程,Reactor模型,和Java多執行緒編碼的把握。首先,需要知道BIO和NIO的區別,NIO三大件的特點和用途,原生NIO的bug,比如臭名昭著的epoll空輪詢bug、還有一些坑,比如處理I/O事件需要移除key,I/O多路復用器(Selector)返回的集合不是執行緒安全的,如何正確的給Selector註冊Channel和更新感興趣的I/O事件,如何正確的處理寫事件,如何正確的處理連接事件,如何正確的取消註冊Channel等,太多了,以致於能深刻理解為何Netty會這麼火。之後就是對Reactor三種模型的正確理解,尤其是主從模型的理解,以及Netty默認使用的是多執行緒Reactor模型。然後就是對JUC的使用,常見無鎖工具的理解,如何配置I/O執行緒池,Netty的NIO執行緒的事件循環機制等,最後就是對Linux的5種I/O模型的認識,尤其是I/O多路復用模型中的epoll機制的深刻把握。

對應Netty就是Channel,Unsafe,EventLoop(Group)組件。

 

2、非同步影像

即把握對觀察者設計模式,多執行緒設計模式中的Future,承諾模式的理解,還有JUC的管程,鎖的機制的理解等。

對應Netty就是Future和Promise組件。

 

3、流水線影像(pipeline+事件回調影像)

這是後續要分析總結的pipeline模型和事件回調機制,這部分需要知道Netty的pipeline模型和職責鏈設計模式的區別,pipeline的雙向鏈表結構,入站和出站處理器的設計理念和各個回調事件的正確使用,還有一些Netty已經實現好的常用的入站,出站處理器的正確使用和理解。

對應Netty就是ChannelPipeline和ChannelHandler組件

 

4、記憶體影像

也是後續要分析總結的,核心就是把握Netty的ByteBuf設計理念,和NIO的Buffer的缺陷,重點是ByteBuf的記憶體池設計思想,Netty垃圾回收計數器的設計和編碼注意事項,堆外記憶體的使用套路,這部分重要程度和執行緒調度模型並駕齊驅,甚至還在之上,可以說深刻掌握了記憶體影像才算掌握了Netty,需要知道Netty有哪些記憶體類型,不同類型不同大小的記憶體是如何分配的,不同類型記憶體的回收策略,如何避免OOM,如何減少多執行緒對記憶體分配的競爭,所謂的Netty的零拷貝和作業系統的零拷貝的區別等。

 

以上,掌握了這幾個影像,個人認為Netty就應該算掌握的比較不錯了,剩下的就是對Netty的高性能工具類的學習和對其實現源碼的把握,比如時間輪工具——HashedWheelTimer,改進的FastThreadLocal,記憶體池,常量池,@Sharable註解在何時可以使用以及坑。

 

進一步,需要掌握Netty的一些高級特性的實現原理和用法,比如空閑檢測handler,並且知道如何設計長連接以及對應的心跳包和應用層心跳的必要性,知道如何斷鏈重連,如何設計重複登錄保護機制,還要知道Netty所提供的TCP協議的粘包半包處理器的使用方法和底層原理,流控和流量整形的使用方法,並且知道流控和流量整形的區別以及各自的實現機制。

 

再進一步,就是熟悉各種開源框架中如何使用的Netty,比如Zookeeper,Kafka,Elasticsearch,gRpc等開源框架是怎麼用Netty的。

 

個人應該正處在上個階段。最後再往下鑽只能先拋磚引玉,因為可能單純靠看源碼或者文章會力不從心,至少我是這樣感覺的,所以需要進一步用實際的高並發項目打磨。比如IM項目,遊戲伺服器,推送服務等,這樣應該能更深刻的理解比如私有協議的一般設計套路,壓縮合併handler的技巧,單機百萬長連接的調優過程,即著名的C10K到C10M問題,Netty的性能指標和監控策略,需要對Linux作業系統和TCP協議有一定認識,比如常用的TCP參數的深入理解,可以通過如下幾個問題,也是我收集的很常見的面試題來自我評判,後續專題總結出來:

1、TCP協議如何保證可靠性

2、TCP協議如何對發送的數據進行分段?

3、TCP協議有幾種狀態,都是什麼,是如何轉換的?

4、三次握手中,如果服務端TCP狀態為SYN_RECEV態,此時發普通數據包給伺服器,伺服器會怎麼處理?

5、TCP連接何時會進入TIME-WAIT態,該狀態會如何轉移?

6、TCP四次分手,最後為什麼要等待2MSL後才關閉連接,為何不是4MSL或者其它時間?

7、TCP連接的關閉過程由於存在TIME-WAIT態,這會影響其他伺服器程式在該埠建立TCP連接嗎?

8、TCP被動關閉方如果總收不到對端最後一個ACK,那會一直重傳FIN段么?

9、TCP主動關閉方有沒有可能等待2MSL後,收到對端的超時重傳FIN報文?

10、如何理解IP數據報的TTL?

11、TCP的被動關閉端為什麼不需要類似TIME_WAIT的狀態?

12、什麼是TCP的全連接,半連接隊列?

13、如何關閉TCP連接,怎麼優雅的關閉?

14、為什麼TCP有一個SO_REUSEADDR參數,它對網路編程有什麼影響?

15、伺服器TIME_WAIT狀態過多怎麼辦,如何定位並解決?

16、TCP的RST標誌位在何時會出現?

17、TCP的RST攻擊是怎麼回事?

18、Java的IOException:Connection reset by peer的真正原因是什麼?

19、TCP的SYN Flood攻擊是怎麼回事,如何解決?

20、TCP中已有SO_KEEPALIVE選項,為什麼還在應用層加入心跳機制?

21、TCP如何處理小塊的數據流(涉及nagle演算法)?

22、TCP如何處理大塊數據流(涉及滑動窗口,擁塞控制演算法)?

23、TCP協議的7個定時器是哪幾個,分別在什麼條件下起作用?

24、TCP協議的應用層的粘包、分包問題和解決方案

25、TCP協議存在哪些缺陷?

26、TCP/IP和HTTP的區別和聯繫?

27、一個應用最多可以支援多少TCP並發連接?

28、單機百萬長連接的調優過程,涉及作業系統的一些參數+TCP參數+Netty參數+JVM參數的配置

29、某個應用的CPU使用率很高,甚至達到100%,應該怎麼處理?

30、什麼是殭屍進程,大量的殭屍進程或者不可中斷進程該怎麼處理?

31、如何分析CPU的瓶頸?

32、伺服器的記憶體swap變高了應該怎麼處理?

33、JVM發生了OOM該怎麼定位和處理,有哪些命令和工具?

34、。。。

 

以上,篇幅有限,想到哪兒寫到哪兒,關於Netty的執行緒調度,I/O多路復用器,非同步API的拆解算是告一段落,接下來的幾篇文章分析總結的是Netty服務端新連接接入的過程和Netty的pipeline+事件回調機制。

 

4句詩與君共享:

1、問渠那得清如許,為有源頭活水來

2、沉舟側畔千帆過,病樹前頭萬木春

3、革命尚未成功,同志仍需努力

4、低頭做事,抬頭看路,既要深入細節,又要跳出來看全局

 

後記

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