大數據、分佈式都用到了的Netty,這幾大核心知識你必定要看看!

1. Netty 基礎

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

2. Netty 高性能之道

2.1. RPC 調用的性能模型分析

2.1.1. 傳統 RPC 調用性能差的三宗罪

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

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

圖2-1 BIO 通訊模型圖編程

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

序列化方式問題:Java 序列化存在以下幾個典型問題:安全

  1. Java 序列化機制是 Java 內部的一種對象編解碼技術,沒法跨語言使用;例如對於異構系統之間的對接,Java 序列化後的碼流須要可以經過其它語言反序列化成原始對象(副本),目前很難支持;服務器

  2. 相比於其它開源的序列化框架,Java 序列化後的碼流太大,不管是網絡傳輸仍是持久化到磁盤,都會致使額外的資源佔用;網絡

  3. 序列化性能差(CPU 資源佔用高)。多線程

線程模型問題:因爲採用同步阻塞 IO,這會致使每一個 TCP 鏈接都佔用1個線程,因爲線程資源是 JVM 虛擬機很是寶貴的資源,當 IO 讀寫阻塞致使線程沒法及時釋放時,會致使系統性能急劇降低,嚴重的甚至會致使虛擬機沒法建立新的線程。架構

2.1.2. 高性能的三個主題

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

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

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

圖2-2 RPC 調用性能三要素

2.2. Netty 高性能之道

2.2.1. 異步非阻塞通訊

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

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

JDK NIO 通訊模型以下所示:

圖2-3 NIO 的多路複用模型圖

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

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

圖2-3 NIO 服務端通訊序列圖

客戶端通訊序列圖以下:

圖2-4 NIO 客戶端通訊序列圖

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

2.2.2. 零拷貝技術

不少用戶都據說過 Netty 具備「零拷貝」功能,可是具體體如今哪裏又說不清楚,本小節就詳細對 Netty 的「零拷貝」功能進行講解。

Netty 的「零拷貝」主要體如今以下三個方面:

  1. Netty 的接收和發送 ByteBuffer 採用 DIRECT BUFFERS,使用堆外直接內存進行 Socket 讀寫,不須要進行字節緩衝區的二次拷貝。若是使用傳統的堆內存(HEAP BUFFERS)進行 Socket 讀寫,JVM 會將堆內存 Buffer 拷貝一份到直接內存中,而後才寫入 Socket 中。相比於堆外直接內存,消息在發送過程當中多了一次緩衝區的內存拷貝。

  2. Netty 提供了組合 Buffer 對象,能夠聚合多個 ByteBuffer 對象,用戶能夠像操做一個 Buffer 那樣方便的對組合 Buffer 進行操做,避免了傳統經過內存拷貝的方式將幾個小 Buffer 合併成一個大的 Buffer。

  3. Netty 的文件傳輸採用了 transferTo 方法,它能夠直接將文件緩衝區的數據發送到目標 Channel,避免了傳統經過循環 write 方式致使的內存拷貝問題。

下面,咱們對上述三種「零拷貝」進行說明,先看 Netty 接收 Buffer 的建立:

圖2-5 異步消息讀取「零拷貝」

每循環讀取一次消息,就經過 ByteBufAllocator的ioBuffer 方法獲取 ByteBuf 對象,下面繼續看它的接口定義:

圖2-6 ByteBufAllocator 經過 ioBuffer 分配堆外內存

當進行 Socket IO 讀寫的時候,爲了不從堆內存拷貝一份副本到直接內存,Netty 的 ByteBuf 分配器直接建立非堆內存避免緩衝區的二次拷貝,經過「零拷貝」來提高讀寫性能。

下面咱們繼續看第二種「零拷貝」的實現 CompositeByteBuf,它對外將多個 ByteBuf 封裝成一個 ByteBuf,對外提供統一封裝後的 ByteBuf 接口,它的類定義以下:

圖2-7 CompositeByteBuf 類繼承關係

經過繼承關係咱們能夠看出 CompositeByteBuf 實際就是個 ByteBuf 的包裝器,它將多個 ByteBuf 組合成一個集合,而後對外提供統一的 ByteBuf 接口,相關定義以下:

圖2-8 CompositeByteBuf 類定義

添加 ByteBuf,不須要作內存拷貝,相關代碼以下:

圖2-9 新增 ByteBuf 的「零拷貝」

最後,咱們看下文件傳輸的「零拷貝」:

圖2-10 文件傳輸「零拷貝」

Netty 文件傳輸 DefaultFileRegion 經過 transferTo 方法將文件發送到目標 Channel 中,下面重點看 FileChannel 的 transferTo 方法,它的 API DOC 說明以下:

圖2-11 文件傳輸 「零拷貝」

對於不少操做系統它直接將文件緩衝區的內容發送到目標 Channel 中,而不須要經過拷貝的方式,這是一種更加高效的傳輸方式,它實現了文件傳輸的「零拷貝」。

2.2.3. 內存池

隨着 JVM 虛擬機和 JIT 即時編譯技術的發展,對象的分配和回收是個很是輕量級的工做。可是對於緩衝區 Buffer,狀況卻稍有不一樣,特別是對於堆外直接內存的分配和回收,是一件耗時的操做。爲了儘可能重用緩衝區,Netty 提供了基於內存池的緩衝區重用機制。下面咱們一塊兒看下 Netty ByteBuf 的實現:

圖2-12 內存池 ByteBuf

Netty 提供了多種內存管理策略,經過在啓動輔助類中配置相關參數,能夠實現差別化的定製。

下面經過性能測試,咱們看下基於內存池循環利用的 ByteBuf 和普通 ByteBuf 的性能差別。

用例一,使用內存池分配器建立直接內存緩衝區:

圖2-13 基於內存池的非堆內存緩衝區測試用例

用例二,使用非堆內存分配器建立的直接內存緩衝區:

圖2-14 基於非內存池建立的非堆內存緩衝區測試用例

各執行300萬次,性能對比結果以下所示:

圖2-15 內存池和非內存池緩衝區寫入性能對比

性能測試代表,採用內存池的 ByteBuf 相比於朝生夕滅的 ByteBuf,性能高23倍左右(性能數據與使用場景強相關)。

下面咱們一塊兒簡單分析下 Netty 內存池的內存分配:

圖2-16 AbstractByteBufAllocator 的緩衝區分配

繼續看 newDirectBuffer 方法,咱們發現它是一個抽象方法,由 AbstractByteBufAllocator 的子類負責具體實現,代碼以下:

圖2-17 newDirectBuffer 的不一樣實現

代碼跳轉到 PooledByteBufAllocator 的 newDirectBuffer 方法,從 Cache 中獲取內存區域 PoolArena,調用它的 allocate 方法進行內存分配:

圖2-18 PooledByteBufAllocator 的內存分配

PoolArena 的 allocate 方法以下:

圖2-18 PoolArena 的緩衝區分配

咱們重點分析 newByteBuf 的實現,它一樣是個抽象方法,由子類 DirectArena 和 HeapArena 來實現不一樣類型的緩衝區分配,因爲測試用例使用的是堆外內存,

圖2-19 PoolArena 的 newByteBuf 抽象方法

所以重點分析 DirectArena 的實現:若是沒有開啓使用 sun 的 unsafe,則

圖2-20 DirectArena 的 newByteBuf 方法實現

執行 PooledDirectByteBuf 的 newInstance 方法,代碼以下:

圖2-21 PooledDirectByteBuf 的 newInstance 方法實現

經過 RECYCLER 的 get 方法循環使用 ByteBuf 對象,若是是非內存池實現,則直接建立一個新的 ByteBuf 對象。從緩衝池中獲取 ByteBuf 以後,調用 AbstractReferenceCountedByteBuf的setRefCnt 方法設置引用計數器,用於對象的引用計數和內存回收(相似 JVM 垃圾回收機制)。

2.2.4. 高效的 Reactor 線程模型(重點)

經常使用的 Reactor 線程模型有三種,分別以下:

  1. Reactor 單線程模型;

  2. Reactor 多線程模型;

  3. 主從 Reactor 多線程模型(很重點)

Reactor 單線程模型,指的是全部的 IO 操做都在同一個 NIO 線程上面完成,NIO 線程的職責以下:

  1. 做爲 NIO 服務端,接收客戶端的 TCP 鏈接;

  2. 做爲 NIO 客戶端,向服務端發起 TCP 鏈接;

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

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

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

圖2-22 Reactor 單線程模型

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

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

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

  2. 當 NIO 線程負載太重以後,處理速度將變慢,這會致使大量客戶端鏈接超時,超時以後每每會進行重發,這更加劇了 NIO 線程的負載,最終會致使大量消息積壓和處理超時,NIO 線程會成爲系統的性能瓶頸;

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

爲了解決這些問題,演進出了 Reactor 多線程模型,下面咱們一塊兒學習下 Reactor 多線程模型。

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

圖2-23 Reactor 多線程模型

Reactor 多線程模型的特色:

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

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

  3. 1個 NIO 線程能夠同時處理 N 條鏈路,可是1個鏈路只對應1個 NIO 線程,防止發生併發操做問題。

在絕大多數場景下,Reactor 多線程模型均可以知足性能需求;可是,在極特殊應用場景中,一個 NIO 線程負責監聽和處理全部的客戶端鏈接可能會存在性能問題。例如百萬客戶端併發鏈接,或者服務端須要對客戶端的握手消息進行安全認證,認證自己很是損耗性能。在這類場景下,單獨一個 Acceptor 線程可能會存在性能不足問題,爲了解決性能問題,產生了第三種 Reactor 線程模型-主從 Reactor 多線程模型。

主從 Reactor 線程模型的特色是:服務端用於接收客戶端鏈接的再也不是個1個單獨的 NIO 線程,而是一個獨立的 NIO 線程池。Acceptor 接收到客戶端 TCP 鏈接請求處理完成後(可能包含接入認證等),將新建立的 SocketChannel 註冊到 IO 線程池(sub reactor 線程池)的某個 IO 線程上,由它負責 SocketChannel 的讀寫和編解碼工做。Acceptor 線程池僅僅只用於客戶端的登錄、握手和安全認證,一旦鏈路創建成功,就將鏈路註冊到後端 subReactor 線程池的 IO 線程上,由 IO 線程負責後續的 IO 操做。

它的線程模型以下圖所示:

圖2-24 Reactor 主從多線程模型

利用主從 NIO 線程模型,能夠解決1個服務端監聽線程沒法有效處理全部客戶端鏈接的性能不足問題。所以,在 Netty 的官方 demo 中,推薦使用該線程模型。

事實上,Netty 的線程模型並不是固定不變,經過在啓動輔助類中建立不一樣的 EventLoopGroup 實例並經過適當的參數配置,就能夠支持上述三種 Reactor 線程模型。正是由於 Netty 對 Reactor 線程模型的支持提供了靈活的定製能力,因此能夠知足不一樣業務場景的性能訴求。

2.2.5. 無鎖化的串行設計理念

在大多數場景下,並行多線程處理能夠提高系統的併發性能。可是,若是對於共享資源的併發訪問處理不當,會帶來嚴重的鎖競爭,這最終會致使性能的降低。爲了儘量的避免鎖競爭帶來的性能損耗,能夠經過串行化設計,即消息的處理儘量在同一個線程內完成,期間不進行線程切換,這樣就避免了多線程競爭和同步鎖。

爲了儘量提高性能,Netty 採用了串行無鎖化設計,在 IO 線程內部進行串行操做,避免多線程競爭致使的性能降低。表面上看,串行化設計彷佛 CPU 利用率不高,併發程度不夠。可是,經過調整 NIO 線程池的線程參數,能夠同時啓動多個串行化的線程並行運行,這種局部無鎖化的串行線程設計相比一個隊列-多個工做線程模型性能更優。

Netty 的串行化設計工做原理圖以下:

圖2-25 Netty 串行化工做原理圖

Netty 的 NioEventLoop 讀取到消息以後,直接調用 ChannelPipeline 的 fireChannelRead(Object msg),只要用戶不主動切換線程,一直會由 NioEventLoop 調用到用戶的 Handler,期間不進行線程切換,這種串行化處理方式避免了多線程操做致使的鎖的競爭,從性能角度看是最優的。

2.2.6 靈活的 TCP 參數配置能力

合理設置 TCP 參數在某些場景下對於性能的提高能夠起到顯著的效果,例如 SO_RCVBUF 和 SO_SNDBUF。若是設置不當,對性能的影響是很是大的。下面咱們總結下對性能影響比較大的幾個配置項:

  1. SO_RCVBUF 和 SO_SNDBUF:一般建議值爲 128 K 或者 256 K;

  2. SO_TCPNODELAY:NAGLE 算法經過將緩衝區內的小封包自動相連,組成較大的封包,阻止大量小封包的發送阻塞網絡,從而提升網絡應用效率。可是對於時延敏感的應用場景須要關閉該優化算法;

  3. 軟中斷:若是 Linux 內核版本支持 RPS(2.6.35以上版本),開啓 RPS 後能夠實現軟中斷,提高網絡吞吐量。RPS 根據數據包的源地址,目的地址以及目的和源端口,計算出一個 hash值,而後根據這個 hash 值來選擇軟中斷運行的 cpu,從上層來看,也就是說將每一個鏈接和 cpu 綁定,並經過這個 hash 值,來均衡軟中斷在多個 cpu 上,提高網絡並行處理性能。

Netty 在啓動輔助類中能夠靈活的配置 TCP 參數,知足不一樣的用戶場景。相關配置接口定義以下:

圖2-27 Netty 的 TCP 參數配置定義

2.3. 總結

經過對 Netty 的架構和性能模型進行分析,咱們發現 Netty 架構的高性能是被精心設計和實現的,得益於高質量的架構和代碼,Netty 支持 10W TPS 的跨節點服務調用並非件十分困難的事情。

歡迎關注:《老男孩的成長之路》,後臺私信「資料」領取《Java面試寶典Plus》版

相關文章
相關標籤/搜索