分析Netty線程模型

Netty線程模型是基於Reactor線程模型的,而Reactor線程模型又分爲單線程模型,多線程模型,主從Reactor多線程模型。Reactor的線程模型是基於同步非阻塞的IO實現的,基於異步非阻塞的IO實現的是Proactor(這裏我們不分析Proactor)。下面一起來研究一下Reactor線程模型,以及其在Netty線程模型中的應用。

1.Reactor單線程模型

原理圖如下:


Reactor單線程模型,顧名思義就是由單個NIO線程來完成所有的IO操作,具體的工作有:

a.負責讀取通訊對端的請求/應答消息

b.負責向通訊對端發送請求/應答消息

c.作爲服務端,接收客戶端的連接請求

d.作爲客戶端,向服務端發送連接請求

從原理圖中可以看到,NIO一個線程把多路複用,事件分發器和處理都自己全部包攬了,Reactor線程模式採用的是同步非阻塞IO,即線程上的IO操作不會受到阻塞的影響,理論上來說,上面的工作,的確可以由一個線程全部搞定(select會主動輪詢哪些IO操作就緒),工作過程:Acceptor接收客戶端發來的TCP請求,鏈路成功建立之後,通過dispatcher將ByteBuf交給對應的Handler進行處理。

對於一些小流量的應用程序,可以採用上述單線程模型,但是對於大負載、高併發的情況卻不適用,主要原因有:

a.當一個IO線程處理百萬甚至千萬級別的鏈路數量時,性能會支撐不足,CPU也可能會被壓垮

b.當一個IO線程負載過重,就會出現處理不及時的情況,會導致客戶端長時間等待而不斷超時重連,客戶體驗極差

c.一旦這個IO線程出現了死循環,則會導致整個程序不可用。

爲了彌補上述短板,提出了Reactor多線程模型

2.Reactor多線程模型

原理圖如下:



從原理圖中分析如下:

a.有一個專門的NIO線程Acceptor負責監聽服務器,處理客戶端的TCP連接,包括安全驗證等

b.Acceptor處理完之後,將IO操作發到NIO線程池,線程池可使用JDK的線程池實現,包含一個任務隊列和N個線程,這些線程負責信息的讀取、編碼、解碼、發送

c.一個NIO線程可以同時處理N個鏈路,但一個鏈路只能對應一個線程,避免了併發的問題

Reactor多線程模型滿足絕大部分的應用場景,但不足之處在於,當面臨百萬級別的請求當中需要有安全認證時,因爲認證是消耗性能的操作,所以一個Acceptor線程會出現性能瓶頸。

爲了解決上述問題,出現了主從Reactor多線程模型

3.主從Reactor多線程模型

原理圖如下:


從原理圖中以看出,處理客戶端連接請求的不在是一個NIO線程,而是一個NIO線程池,Acceptor線程處理完客戶端的TCP接入請求(可能包含驗證)之後,再將新創建的SocketChannel註冊到IO線程池(Sub Reactor)中某個IO線程來處理讀取、編碼、解碼、發送等操作。Acceptor線程只負責監聽服務端,處理客戶端的TCP連接與認證請求,鏈路成功建立之後,便把鏈路註冊到Sub Reactor Thread Pool的線程中。

主從Reactor多線程模型,很多地解決性能瓶頸的問題,這時Netty推薦使用的線程模型

4.Netty的線程模型

原理圖如下:


Netty的線程模型可以通過設置啓動類參數來選擇線程模型,netty支持Reactor的單線程模型、多線程、主從Reactor多線程模型。

從原理圖中可以看到,服務端啓動時創建了兩個NioEventLoopGroup,這是兩個相互獨立的Reactor線程池,一個是boss線程池,一個是work線程池。兩個分工明確,一個負責接收客戶端連接請求,一個負責處理IO相關的讀寫操作,或者執行Task任務,或執行定時Task任務。其中NioEventLoopGroup中的NioEventLoop個數默認爲處理器核數*2。

通過配置boss線程池和worker線程池的線程個數以及是否共享線程池,來配置單線程、多線程、主從Reactor多線程。

Boss線程池的任務:

a.接收客戶端的連接請求,初始化channel參數

b.將鏈路狀態變化時間通知給ChannelPipeline

Worker線程池的作用

a.異步讀取通信對端的消息,發送讀事件到ChannelPipeline

b.異步發送消息到通信對端,調用ChannelPipeline的發送消息接口

c.執行系統Task任務

d.執行系統定時Task任務

4.1系統Task

創建它們的原因是當IO線程和用戶線程都在操作同一個資源時,會發生鎖競爭的問題,所以將用戶線程封裝爲一個Task,交給IO線程串行處理,實現局部無鎖化

4.2定時Task

主要用於監控和檢查等定時動作

基於以上原因,NioEventLoop不僅僅是一個IO線程,它還負責用戶線程的調度。

NioEventLoop處理鏈原理圖如下:

爲了提升性能,Netty內部許多地方都採用了局部無鎖化設計,比如IO線程內部採用串行化操作來避免多線程的鎖競爭問題。表面上看這樣會導致CUP利用率低下,但只要配置多個串行化IO線程併發操作,就能提高CUP利用率,且性能比更優。

Netty的NioEventLoop讀取完數據之後,將直接調用ChannelPipeline的fireChannelRead(Object msg)方法,只要用戶不切換線程,NioEventLoop將一直調用用戶的handler,期間不進行線程切換。這就是串行化的具體表現,可以很好地避免了鎖競爭的問題,提升了性能。

4.3Netty線程模型設置的要點

a.創建兩個NioEventLoopGroup,分別爲NIO Acceptor線程池和NIO IO線程池

b.編解碼需要放置在NIO的Handler中進行,而不是用戶線程中

c.儘量不要在ChannelHandler中創建用戶線程,可以在解碼之後,將POJO派發到後端業務線程池

d.如果IO操作不復雜,沒有涉及可能阻塞的數據庫讀取、磁盤讀取、網絡讀寫等操作,可以直接在NIO調用的handler中完成業務邏輯,而不需要放在用戶線程中進行,相反地,如果IO業務邏輯複雜, 將會嚴重影響性能,所以需要將POJO打包成Task對象,派發到業務線程池中處理,確保NIO線程不被長時間佔用而導致假死。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章