​結合異步模型,再次總結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電子書,資料,幫忙內推,歡迎拍磚!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM