Netty源碼分析之Reactor線程模型源碼分析

Netty源碼分析之Reactor線程模型源碼分析

多線程

隨著硬件性能的提升,CPU的核數越來越越多,很多服務器標配已經達到32或64核。通過多線程併發編程,可以充分利用多核CPU的處理能力,提升系統的處理效率和併發性能。

從2005年開始,隨著多核處理器的逐步普及,java的多線程併發編程也逐漸流行起來,當時商用主流的JDK版本是1.4,用戶可以通過 new Thread()的方式創建新的線程。

由於JDK1.4並沒有提供類似線程池這樣的線程管理容器,多線程之間的同步、協作、創建和銷燬等工作都需要用戶自己實現。由於創建和銷燬線程是個相對比較重量級的操作,因此,這種原始的多線程編程效率和性能都不高。

線程池

為了提升Java多線程編程的效率和性能,降低用戶開發難度。JDK1.5推出了java.util.concurrent併發編程包。在併發編程類庫中,提供了線程池、線程安全容器、原子類等新的類庫,極大的提升了Java多線程編程的效率,降低了開發難度。

從JDK1.5開始,基於線程池的併發編程已經成為Java多核編程的主流。

單線程模型

Reactor單線程模型,指的是所有的IO操作都在同一個NIO線程上面完成,NIO線程的職責如下:

1)作為NIO服務端,接收客戶端的TCP連接;

2)作為NIO客戶端,向服務端發起TCP連接;

3)讀取通信對端的請求或者應答消息;

4)向通信對端發送消息請求或者應答消息。

Reactor單線程模型示意圖如下所示:

Netty源碼分析之Reactor線程模型源碼分析

Reactor單線程模型

由於Reactor模式使用的是異步非阻塞IO,所有的IO操作都不會導致阻塞,理論上一個線程可以獨立處理所有IO相關的操作。從架構層面看,一個NIO線程確實可以完成其承擔的職責。例如,通過Acceptor類接收客戶端的TCP連接請求消息,鏈路建立成功之後,通過Dispatch將對應的ByteBuffer派發到指定的Handler上進行消息解碼。用戶線程可以通過消息編碼通過NIO線程將消息發送給客戶端。

對於一些小容量應用場景,可以使用單線程模型。但是對於高負載、大併發的應用場景卻不合適,主要原因如下:

1)一個NIO線程同時處理成百上千的鏈路,性能上無法支撐,即便NIO線程的CPU負荷達到100%,也無法滿足海量消息的編碼、解碼、讀取和發送;

2)當NIO線程負載過重之後,處理速度將變慢,這會導致大量客戶端連接超時,超時之後往往會進行重發,這更加重了NIO線程的負載,最終會導致大量消息積壓和處理超時,成為系統的性能瓶頸;

3)可靠性問題:一旦NIO線程意外跑飛,或者進入死循環,會導致整個系統通信模塊不可用,不能接收和處理外部消息,造成節點故障。

為了解決這些問題,演進出了Reactor多線程模型,下面我們一起學習下Reactor多線程模型。

多線程模型

Rector多線程模型與單線程模型最大的區別就是有一組NIO線程處理IO操作,它的原理圖如下:

Netty源碼分析之Reactor線程模型源碼分析

Reactor多線程模型

Reactor多線程模型的特點:

1)有專門一個NIO線程-Acceptor線程用於監聽服務端,接收客戶端的TCP連接請求;

2)網絡IO操作-讀、寫等由一個NIO線程池負責,線程池可以採用標準的JDK線程池實現,它包含一個任務隊列和N個可用的線程,由這些NIO線程負責消息的讀取、解碼、編碼和發送;

3)1個NIO線程可以同時處理N條鏈路,但是1個鏈路只對應1個NIO線程,防止發生併發操作問題。

在絕大多數場景下,Reactor多線程模型都可以滿足性能需求;但是,在極個別特殊場景中,一個NIO線程負責監聽和處理所有的客戶端連接可能會存在性能問題。例如併發百萬客戶端連接,或者服務端需要對客戶端握手進行安全認證,但是認證本身非常損耗性能。在這類場景下,單獨一個Acceptor線程可能會存在性能不足問題,為了解決性能問題,產生了第三種Reactor線程模型-主從Reactor多線程模型。

Netty線程模型

Netty是一個高性能、異步事件驅動的NIO框架,它提供了對TCP、UDP和文件傳輸的支持,作為一個異步NIO框架,Netty的所有IO操作都是異步非阻塞的,通過Future-Listener機制,用戶可以方便的主動獲取或者通過通知機制獲得IO操作結果。

作為當前最流行的NIO框架,Netty在互聯網領域、大數據分佈式計算領域、遊戲行業、通信行業等獲得了廣泛的應用,一些業界著名的開源組件也基於Netty的NIO框架構建

傳統通信採用了同步阻塞IO,當客戶端的併發壓力或者網絡時延增大之後,同步阻塞IO會由於頻繁的wait導致IO線程經常性的阻塞,由於線程無法高效的工作,IO處理能力自然下降。

我們通過BIO通信模型圖看下BIO通信的弊端

採用BIO通信模型的服務端,通常由一個獨立的Acceptor線程負責監聽客戶端的連接,接收到客戶端連接之後為客戶端連接創建一個新的線程處理請求消息,處理完成之後,返回應答消息給客戶端,線程銷燬,這就是典型的一請求一應答模型。

該架構最大的問題就是不具備彈性伸縮能力,當併發訪問量增加後,服務端的線程個數和併發訪問數成線性正比,由於線程是JAVA虛擬機非常寶貴的系統資源,當線程數膨脹之後,系統的性能急劇下降,隨著併發量的繼續增加,可能會發生句柄溢出、線程堆棧溢出等問題,並導致服務器最終宕機。

Netty基於NIO,實現了對NIO的封裝及優化,從而Netty的通信模式為異步非阻塞通信

在IO編程過程中,當需要同時處理多個客戶端接入請求時,可以利用多線程或者IO多路複用技術進行處理。IO多路複用技術通過把多個IO的阻塞複用到同一個select的阻塞上,從而使得系統在單線程的情況下可以同時處理多個客戶端請求。與傳統的多線程/多進程模型比,I/O多路複用的最大優勢是系統開銷小,系統不需要創建新的額外進程或者線程,也不需要維護這些進程和線程的運行,降低了系統的維護工作量,節省了系統資源。

JDK1.4提供了對非阻塞IO(NIO)的支持,JDK1.6版本使用epoll替代了傳統的select/poll,極大的提升了NIO通信的性能

Netty架構按照Reactor模式設計和實現,它的服務端通信序列圖如下

Netty的IO線程NioEventLoop由於聚合了多路複用器Selector,可以同時併發處理成百上千個客戶端Channel,由於讀寫操作都是非阻塞的,這就可以充分提升IO線程的運行效率,避免由於頻繁IO阻塞導致的線程掛起。另外,由於Netty採用了異步通信模式,一個IO線程可以併發處理N個客戶端連接和讀寫操作,這從根本上解決了傳統同步阻塞IO一連接一線程模型,架構的性能、彈性伸縮能力和可靠性都得到了極大的提升。

何為Reactor線程模型?

從結構上,這有點類似生產者消費者模式,即有一個或多個生產者將事件放入一個Queue中,而一個或多個消費者主動的從這個Queue中Poll事件來處理;而Reactor模式則並沒有Queue來做緩衝,每當一個Event輸入到Service Handler之後,該Service Handler會立刻的根據不同的Event類型將其分發給對應的Request Handler來處理。

這個做的好處有很多,首先我們可以將處理event的Request handler實現一個單獨的線程,即

這樣Service Handler 和request Handler實現了異步,加快了service Handler處理event的速度,那麼每一個request同樣也可以以多線程的形式來處理自己的event,即Thread1 擴展成Thread pool 1,

Netty的Reactor線程模型1 Reactor單線程模型 Reactor機制中保證每次讀寫能非阻塞讀寫

一個線程(單線程)來處理CONNECT事件(Acceptor),一個線程池(多線程)來處理read,一個線程池(多線程)來處理write,那麼從Reactor Thread到handler都是異步的,從而IO操作也多線程化。

到這裡跟BIO對比已經提升了很大的性能,但是還可以繼續提升,由於Reactor Thread依然為單線程,從性能上考慮依然有所限制

2 Reactor多線程模型

這樣通過Reactor Thread Pool來提高event的分發能力

3 Reactor主從模型

Netty的高效併發編程主要體現在如下幾點:

1) volatile的大量、正確使用;

2) CAS和原子類的廣泛使用;

3) 線程安全容器的使用;

4) 通過讀寫鎖提升併發性能。

Netty除了使用reactor來提升性能,當然還有

1、零拷貝,IO性能優化

2、通信上的粘包拆包

2、同步的設計

3、高性能的序列

關注、轉發、評論頭條號每天分享java知識,私信回覆“555”贈送一些Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式資料


分享到:


相關文章: