深刻了解 Java-Netty高性能高併發理解

一丶 Netty基礎入門react

Netty是一個高性能、異步事件驅動的NIO框架,它提供了對TCP、UDP和文件傳輸的支持,做爲一個異步NIO框架,Netty的全部IO操做都是異步非阻塞的,經過Future-Listener機制,用戶能夠方便的主動獲取或者經過通知機制得到IO操做結果。程序員

做爲當前最流行的NIO框架,Netty在互聯網領域、大數據分佈式計算領域、遊戲行業、通訊行業等得到了普遍的應用,一些業界著名的開源組件也基於Netty的NIO框架構建。算法

二丶 Netty高性能之道sql

RPC調用的性能模型分析編程

RPC 的全稱是 Remote Procedure Call 是一種進程間通訊方式。 它容許程序調用另外一個地址空間(一般是共享網絡的另外一臺機器上)的過程或函數,而不用程序員顯式編碼這個遠程調用的細節。即程序員不管是調用本地的仍是遠程的函數,本質上編寫的調用代碼基本相同。安全

咱們追溯下當初開發 RPC 的原動機是什麼?在 Nelson 的論文 Implementing Remote Procedure Calls(參考[2]) 中他提到了幾點:性能優化

簡單:RPC 概念的語義十分清晰和簡單,這樣創建分佈式計算就更容易。服務器

高效:過程調用看起來十分簡單並且高效。網絡

通用:在單機計算中「過程」每每是不一樣算法部分間最重要的通訊機制。多線程

通俗一點說,就是通常程序員對於本地的過程調用很熟悉,那麼咱們把 RPC 作成和本地調用徹底相似,那麼就更容易被接受,使用起來毫無障礙。 Nelson 的論文發表於 30 年前,其觀點今天看來確實高瞻遠矚,今天咱們使用的 RPC 框架基本就是按這個目標來實現的。

傳統RPC調用性能差的三大誤區

網絡傳輸方式問題:傳統的RPC框架或者基於RMI等方式的遠程服務(過程)調用採用了同步阻塞IO,當客戶端的併發壓力或者網絡時延增大以後,同步阻塞IO會因爲頻繁的wait致使IO線程常常性的阻塞,因爲線程沒法高效的工做,IO處理能力天然降低。

下面,咱們經過BIO通訊模型圖看下BIO通訊的弊端:

 

BIO通訊模型圖

採用BIO通訊模型的服務端,一般由一個獨立的Acceptor線程負責監聽客戶端的鏈接,接收到客戶端鏈接以後爲客戶端鏈接建立一個新的線程處理請求消息,處理完成以後,返回應答消息給客戶端,線程銷燬,這就是典型的一請求一應答模型。該架構最大的問題就是不具有彈性伸縮能力,當併發訪問量增長後,服務端的線程個數和併發訪問數成線性正比,因爲線程是JAVA虛擬機很是寶貴的系統資源,當線程數膨脹以後,系統的性能急劇降低,隨着併發量的繼續增長,可能會發生句柄溢出、線程堆棧溢出等問題,並致使服務器最終宕機。

高性能的三大要素

1) 傳輸:用什麼樣的通道將數據發送給對方,BIO、NIO或者AIO,IO模型在很大程度上決定了框架的性能。

2) 協議:採用什麼樣的通訊協議,HTTP或者內部私有協議。協議的選擇不一樣,性能模型也不一樣。相比於公有協議,內部私有協議的性能一般能夠被設計的更優。

3) 線程:數據報如何讀取?讀取以後的編解碼在哪一個線程進行,編解碼後的消息如何派發,Reactor線程模型的不一樣,對性能的影響也很是大。

異步非阻塞通訊

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

NIO的多路複用模型圖

與Socket類和ServerSocket類相對應,NIO也提供了SocketChannel和ServerSocketChannel兩種不一樣的套接字通道實現。這兩種新增的通道都支持阻塞和非阻塞兩種模式。阻塞模式使用很是簡單,可是性能和可靠性都很差,非阻塞模式正好相反。開發人員通常能夠根據本身的須要來選擇合適的模式,通常來講,低負載、低併發的應用程序能夠選擇同步阻塞IO以下降編程複雜度。可是對於高負載、高併發的網絡應用,須要使用NIO的非阻塞模式進行開發。

零拷貝

零拷貝是Netty的重要特性之一,而究竟什麼是零拷貝呢?

"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.

從WIKI的定義中,咱們看到「零拷貝」是指計算機操做的過程當中,CPU不須要爲數據在內存之間的拷貝消耗資源。而它一般是指計算機在網絡上發送文件時,不須要將文件內容拷貝到用戶空間(User Space)而直接在內核空間(Kernel Space)中傳輸到網絡的方式。

Non-Zero Copy方式:

Non-Zero Copy方式

Zero Copy方式:

從上圖中能夠清楚的看到,Zero Copy的模式中,避免了數據在用戶空間和內存空間之間的拷貝,從而提升了系統的總體性能。Linux中的sendfile()以及Java NIO中的FileChannel.transferTo()方法都實現了零拷貝的功能,而在Netty中也經過在FileRegion中包裝了NIO的FileChannel.transferTo()方法實現了零拷貝。

而在Netty中還有另外一種形式的零拷貝,即Netty容許咱們將多段數據合併爲一整段虛擬數據供用戶使用,而過程當中不須要對數據進行拷貝操做,這也是咱們今天要講的重點。咱們都知道在stream-based transport(如TCP/IP)的傳輸過程當中,數據包有可能會被從新封裝在不一樣的數據包中,例如當你發送以下數據時:

有可能實際收到的數據以下:

所以在實際應用中,頗有可能一條完整的消息被分割爲多個數據包進行網絡傳輸,而單個的數據包對你而言是沒有意義的,只有當這些數據包組成一條完整的消息時你才能作出正確的處理,而Netty能夠經過零拷貝的方式將這些數據包組合成一條完整的消息供你來使用。而此時,零拷貝的做用範圍僅在用戶空間中。

內存池

爲何要使用內存池?

隨着JVM虛擬機和JIT即時編譯技術的發展,對象的分配和回收是個很是輕量級的工做。可是對於緩衝區Buffer,狀況卻稍有不一樣,特別是對於堆外直接內存的分配和回收,是一件耗時的操做。並且這些實例隨着消息的處理朝生夕滅,這就會給服務器帶來沉重的GC壓力,同時消耗大量的內存。爲了儘可能重用緩衝區,Netty提供了基於內存池的緩衝區重用機制。性能測試代表,採用內存池的ByteBuf相比於朝生夕滅的ByteBuf,性能高23倍左右(性能數據與使用場景強相關)。

如何啓動並初始化內存池?

在Netty4或Netty5中實現了一個新的ByteBuf內存池,它是一個純Java版本的 jemalloc (Facebook也在用)。如今,Netty不會再由於用零填充緩衝區而浪費內存帶寬了。 不過,因爲它不依賴於GC,開發人員須要當心內存泄漏。若是忘記在處理程序中釋放緩衝區,那麼內存使用率會無限地增加。 Netty默認不使用內存池,須要在建立客戶端或者服務端的時候在引導輔助類中進行配置:

work線程配置

如何在本身的業務代碼中使用內存池?

首先,介紹一下Netty的ByteBuf緩衝區的種類:ByteBuf支持堆緩衝區和堆外直接緩衝區,根據經驗來講,底層IO處理線程的緩衝區使用堆外直接緩衝區,減小一次IO複製。業務消息的編解碼使用堆緩衝區,分配效率更高,並且不涉及到內核緩衝區的複製問題。

ByteBuf的堆緩衝區又分爲內存池緩衝區PooledByteBuf和普通內存緩衝區UnpooledHeapByteBuf。PooledByteBuf採用二叉樹來實現一個內存池,集中管理內存的分配和釋放,不用每次使用都新建一個緩衝區對象。UnpooledHeapByteBuf每次都會新建一個緩衝區對象。在高併發的狀況下推薦使用PooledByteBuf,能夠節約內存的分配。在性能可以保證的狀況下,可使用UnpooledHeapByteBuf,實現比較簡單。

在此說明這是當咱們在業務代碼中要使用池化的ByteBuf時的方法:

第一種狀況:若咱們的業務代碼只是爲了將數據寫入ByteBuf中併發送出去,那麼咱們應該使用堆外直接緩衝區DirectBuffer.使用方式以下:

高效的Reactor線程模型

Reactor模式是事件驅動的,有一個或多個併發輸入源,有一個Service Handler,有多個Request Handlers;這個Service Handler會同步的將輸入的請求(Event)多路複用的分發給相應的Request Handler

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

這個作的好處有不少,首先咱們能夠將處理event的Request handler實現一個單獨的線程,即:

Request handler線程

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

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

Reactor單線程模型

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

到這裏跟BIO對比已經提高了很大的性能,可是還能夠繼續提高,因爲Reactor Thread依然爲單線程,從性能上考慮依然有所限制

Reactor多線程模型

Reactor多線程模型

這樣經過Reactor Thread Pool來提升event的分發能力

3 Reactor主從模型

 

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

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

2) CAS和原子類的普遍使用;

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

4) 經過讀寫鎖提高併發性能。

Netty除了使用reactor來提高性能,固然還有

一、零拷貝,IO性能優化

二、通訊上的粘包拆包

二、同步的設計

三、高性能的序列

歡迎工做一到五年的Java工程師朋友們加入Java架構開發:855801563

羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代

相關文章
相關標籤/搜索