Netty原理淺析


一、Netty簡介

1、Netty是異步的、基於事件驅動的網絡應用框架,它以高性能、高並發著稱。基於事件驅動,簡單點說就是 Netty 會根據客戶端的連接請求、讀、寫等事件 做出相應的響應。

2、Netty 主要用於開發基於 TCP 協議的網絡 IO 程序。例如構建高性能RPC,實現高性能服務器/客戶端程序等等。同時Netty也支持UDP、HTTP、WebSocket等多種主流協議。

3、Netty 是基於 Java NIO 構建出來的,NIO是指非阻塞式IO,利用它可以提升並發能力

圖1 是Netty的功能特性圖

1、在傳輸服務方面:它支持TCP UDP傳輸; 支持HTTP 隧道等

2、在協議支持方面: 它支持多種協議如HTTP WebSocket。 並且它提供了一些開箱即用的協議 例如可以用其提供的SSL 方便的進行認證與數據加密解密,利用其提供的zlib/gzip 可以方便的進行數據的壓縮和解壓縮,並且支持了google的protobuf序列化方式。並且支持大文件傳輸,實時的流傳輸

3、它的核心功能包括三方面:

3.1利用其提供的可拓展事件模型,我們可以方便的添加自己的業務邏輯

3.2利用其提供的通用通信API,我們可以告別java NIO 的繁瑣 復雜的代碼

3.3支持零拷貝,零拷貝可以減少數據在內存中的拷貝,可以大幅提高IO性能

1、首先Netty可以用於分布式應用開發中,Netty 作為異步高並發的網絡組件,常用於構建高性能 RPC 框架,以提升分布式服務群之間的服務調用或數據傳輸的並發度和速度。例如阿里 Dubbo 就可以使用 Netty 作為其網絡層

2、Netyy還可以用於大數據基礎設施的構建:比如 Hadoop在處理海量數據的時候,數據在多個計算節點之中傳輸,為了提高傳輸性能,也采用 Netty 構建性能高的網絡 IO 層

3、用Netyy還可以實現 應用層基於公有協議或私有協議的服務器

 

二、Netty原理

零拷貝技術

  1)  Netty 利用了零拷貝技術 提升了IO 性能

  2)   零拷貝指的是 數據在內存中的拷貝次數為0次

  3)  圖2 代表了 磁盤中的一個數據 發給網絡的過程,如果不利用零拷貝 磁盤的數據要先拷貝到內核緩沖區,再拷貝到應用程序內存,再拷貝到Socket緩沖區,最后再發向網絡。不利用零拷貝,數據在內存中拷貝了兩次,一次是內核緩沖區到用戶程序內存,另一次是應用程序內存到Socket緩沖區。

而零拷貝技術,將內核緩沖區 與 應用程序內存 和Socket緩沖區建立了地址映射,這樣數據在內存中的拷貝次數就是0次,減少了拷貝次數,可以大幅提升IO性能。

1) Netty 是基於NIO的,NIO的特點是可以利用一個線程,並發處理多個連接 也稱為IO多路復用

2) 圖3是 NIO 的示意圖,服務器中一個線程可以非阻塞地處理多個客戶端的IO請求。具體過程為服務器為每個客戶端 分配Channel和Buffer,數據是通過通道 Channel 傳輸的,往Channel中讀寫數據需要先經過緩沖區Buffer。接着將每個客戶端對應的Channel的IO事件注冊到多路復用器 Selector上,Selector通過輪詢,就可以找到有IO活動的channel並進行處理,這就是NIO的具體流程。以這種IO處理模式也稱為Reactor模式。

3) 這種模式非阻塞的原因是:若某通道無可用數據,線程不會阻塞在這個通道上等數據准備好,而是可以處理其他通道的讀寫。而傳統的阻塞式IO,采用一個線程對應一個客戶端的方式,若客戶端數據未准備好,則線程一直阻塞。傳統的阻塞式IO,線程利用率不高,且高並發是需要建立大量的線程。而NIO降低了線程數量,提高了線程的利用率 實現了IO 多路復用。Netty 正是利用這種非阻塞式的IO,實現了單個線程就可以並發處理多個連接。

Channel 通道:

1)數據是通過通道傳輸的,它為應用提供I/O操作接口,定義了與socket交互的操作集 比如讀、寫、連接、綁定等。

2)表1是一些常用的 Channel 類型,不同協議、不同的阻塞類型的連接都有不同的 Channel 類型與之對應,,TCP連接中客戶端和服務器用不同的Channel,linux下可以使用EpollSocketChannel建立非阻塞的TCP連接,它是用linux的epoll命令實現的 效率更高。

1)ChannelHandler 通道處理接口:傳遞到通道的數據或者通道傳來的數據要利用ChannelHandler進行處理,例如可以進行編碼、解碼、加密、解密等

2) Netty 中流向Chnannel的有兩個方向的數據,入站數據指的是從網絡發至客戶端或者服務器的數據;出站數據指的是 客戶端或服務器 發到網絡中的數據。

3) 因此也有兩個方向的通道處理接口,ChannelInboundHanlder 繼承自ChanelHandler 專門用於處理入站數據

4) ChanneloutboundHandler 處理出站數據

5)  編碼器都繼承了ChanneloutboundHandler 因為發向網絡的數據一般要先經過編碼,比如說要將對象轉化成字節序列,再在網絡中傳輸。解碼器都繼承了ChannelintboundHandler,因為需要將字節序列轉化成對象。同理,加密繼承於ChanneloutboundHandler,解密繼承於ChannelintboundHandler。

1) 數據處理鏈是包含多個ChannelHandler的雙向鏈表。圖5 是ChannelPipline的示意圖,從網絡中接收的數據從左邊的Socket中傳入ChannelPipline,入站的時候從鏈表頭部,依次傳入所有的ChannelInboundHandler中進行處理。出站的從鏈表尾部依次傳入所有的CahnneloutboundHandler進行處理。

2、 ChannelPipeline其實就是一種高級形式的攔截過濾器。我們可以方便的增加刪除ChannelPipline中的ChannelHanlder,也可以自己實現ChannelHandler,這樣就能完全控制數據從入站到出戰的處理方式,以及各個ChannelHandler 之間的相互交互方式。

1) 一個事件循環對應一個線程,如圖6所示,一個事件循環內維護了一個多路復用器,selector,和一個任務隊列taskQueue。

2) 服務器給每個客戶端分配一個通道Channel,並將該通道的IO事件注冊到Selector上,Selector 用於輪詢各個Channel的IO事件

3) 任務隊列可以異步執行提交的IO任務與非IO任務任務,還可以執行定時任務,比如說我們可以利用任務隊列,向給建立連接的客戶端定時發消息。

 

如圖6所示 EventLoop 其實就循環執行三件事情

1、輪詢注冊在selector上的channel的IO事件

2、在對應的Channel處理IO事件

3、執行任務隊列中的任務

 

每個EventLoop可以負責處理多個Channel上的事件

一個Channel只對應於一個EventLoop (防止並發操作 出現Bug)

 

5) EvenLoopGroup 事件循環組

EvenLoopGroup中含有多個的EventLoop

可以簡單理解為一個線程池,內部維護了一組線程,

EvenLoopGroup 默認初始化 CPU核心數*2 個EventLoop

 6) Bootstrap 引導類

一個Netty應用由一個Bootstrap開始,主要是用來配置整個 Netty 程序、設置業務處理類(Handler)、綁定端口、發起連接等

7) ChannelFuture 異步結果占位符

Netty的I/O操作是異步的,操作可能無法立即返回

ChannelFuture對象作為 異步操作結果的占位符 可確定異步執行的結果

通過addListener方法 可注冊了一個監聽ChannelFutureListener,當操作完成時,自動觸發注冊的監聽事件

 

圖7 是Netty 服務端的工作架構圖: 該圖中有兩個事件循環組:BossGroup 和 WorkerGroup,BossGroup 中的事件循環專門和客戶端建立連接,WorkerGroup 中的EventLoop專門負責處理連接上的讀寫。

在這里,我通過模擬一個客戶端給服務器發消息來解釋圖7:

1、首先初始化ServerSocketChannel 並將建立連接的事件Accept,注冊到BoosGroup的一個事件循環的Selector上

2、接着事件循環就會輪詢Channel上的建立連接事件

3、一個客戶端 發來建立連接請求后,Seletor通過輪詢可以發現此請求,並通過processSeleterKeys 處理處理連接請求

4、怎么處理連接請求呢?首先是為這個連接分配一個SocketChannel,並將這個Channel的讀寫事件注冊到一個WorkerGroup的事件循環的selector上,這時連接就建立好了,並且WorkerGroup會輪詢SocketChannel的讀寫事件。

5、當這個客戶端再發送消息時,事件循環會輪詢到寫事件,並通過processSeleterKeys處理消息

6、processSeleterKeys通過剛剛講的數據處理鏈過ChannelPipline來進行處理,可能包含先解碼、再進行業務處理,再編碼,再發送到SocketChannel中。

以上是服務端的具體流程,客戶端也會建立一個Channel ,也有一個Seletor輪詢IO事件,當消息到達時,也可以通過客戶端的ChannelPipline進行處理。

到現在,我們已經大概了解了Netty的工作原理,BoosGroup 用於專門創建連接,其中有多個事件循環線程,每個事件循環都監聽對應通道的建立連接請求並進行處理。WorkGroup 中也有多個事件循環線程,負責對應通道的IO事件。一個線程可以負責多個通道的IO,實現了IO多路復用。

建立連接、IO處理都由多個線程去做,提高了並發能力,也提高了系統的可靠性 (在之前的單線程處理IO的情況下 若意外終止 則服務不可用)。

 

三、ByteBuf和引用計數

 

Netty 利用ByteBuf作為緩沖區,利用Channel進行讀寫都要經過緩沖區,因此需要了解ByteBuf 的基本概念和操作才能更好的利用Netty編程。

ByteBuf  是存儲字節的容器 類似於NIO中的 ByteBuffer

ByteBuf 中存在

1、寫索引: writerIndex   (當數據寫入ByteBuf時 writerIndex增加)

2、讀索引: readerIndex   (當從ByteBuf讀數據時 readerIndex增加)

當writerIndex==readerIndex時 :代表無數據可以讀

capacity (ByteBuf的容量):默認為Integer.MAX_VALUE

 

因此可將ByteBuf 分為 三個部分

1. 可以被丟棄字節

2. 可讀字節

3. 可寫字節

 

 

ByteBuf 共有三種使用模式

模式1:Heap Buffer(堆緩沖區)

它是將數據存儲在JVM的堆空間(通過將數據存儲在數組中實現)

 堆緩沖區可以通過JVM快速分配與釋放

 

 模式2 :Direct Buffer 直接緩沖區

不在JVM的堆中分配內存,而是在JVM外通過本地方法調用分配虛擬機外內存

優點:免去中間交換的內存拷貝,提升IO處理速度:若在堆,則需要將數據先復制到直接緩沖區,再復制到堆 這體現了Netty的零拷貝特性

 

模式3:Composite Buffer 復合緩沖區

是一種視圖,不實際存數據,它可以由多個堆緩沖區和直接緩沖區 復合組成

優點:可將消息拆分為多個部分,若某部分不變,則不用每次都分配新的緩沖區存不變的部分(向多個客戶端發 相同的消息body不變 header變  可以復用body)

 

有兩種ByteBuf 分配方式,

1.一種是通過ByteBufAllcator類,它可以分配池化或者池化的ByteBuf實例,利用池化技術可以改進性能 降低內存使用率。可以通過channel 和channelhandlercontext 獲得該實例,代碼如圖11所示。
2.第二種分配方式是利用Unpooled類提供的靜態方法,可以創建非池化的ByteBuf實例
 
 

上面我們講到,ByteBuf可以利用直接內存避免拷貝數據到用戶空間,並且Netty還使用池化技術降低內存使用率。因為用到了池化技術,Netty需要將用完的對象放回池中,java的垃圾回收器無法完成此功能,因此引入了引用計數,將用完的對象放回池中。

 

如圖所示 每個對象的初始引用計數為1

buf.retain() ,buf 引用計數加1

buf.release()  ,buf引用計數減1

當引用計數為0時 釋放對象,並返回對象池。

ByteBuf引用計數的原則是:誰最后使用,誰負責釋放

 

Netty提供了檢查內存泄漏的方式,通過配置JVM 的leakDetectionLevel 可以開啟指定級別的泄漏檢測

默認是簡單級別,它會抽樣百分之1的樣本,並告訴我們是否發生內存泄漏。

高級級別可以告訴我們內存泄漏發生的地方。

偏執級別會檢測所有樣本。

 

 

參考資料:

Netty官網

Netty的架構與原理初探

理解高性能網絡模型

Netty試題

Netty實戰精髓

Netty之有效規避內存泄漏

 


免責聲明!

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



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