
一、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之有效規避記憶體泄漏
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/303155.html
標籤:Java
