本文主要從IO模型、Netty邏輯架構、Netty各組件的設計與應用為主導,由簡-難-細展開來介紹,其中包括IO模型以及BIO、NIO、AIO;
邏輯架構主要是如何分層、各層如何協作以及如何達到高性能、高可靠性等;組件主要介紹Buffer、Channel、ChannelPipeling、ChannelHandler、EvnetLoop等,最后從源碼的角度分析客戶端、服務端如何實現以及二者通訊中編解碼、讀寫半包等
- 參考資料
[1]李林鋒《Netty權威指南》 第2版
[2] Netty官網
一、IO模型
-
阻塞IO模型
阻塞IO模型.png
應用進程空間觸發內核調用,數據包準備直到復制到用戶空間或異常發生,此期間應用進程處于等待阻塞狀態,被稱為阻塞模型
-
非阻塞IO模型
非阻塞.png
從應用層到內核,若該緩存區沒有數據,立即返回EWouldBlock異常,通常進行輪詢,反復Recv,直到數據準備完成,與阻塞的區別:無數據的情況下,是否立即返回
-
IO多路復用模型
IO復用模型.png
應用進程通過Select將一個或多個FD(File Description)阻塞,select/poll是順序掃描FD數據是否就緒,Select能持有的FD有限;epoll中FD受限于操作系統的最大文件句柄數,其通過事件驅動替代順序掃描,只關心活躍的FD,當FD就緒,回調rallBack,性能更好,同時epoll通過內核與用戶空間mmap使用同一塊內存來實現的,此期間Select與數據復制都會阻塞應用線程
-
信號驅動IO模型
信號驅動IO模型.png
該模型與復用模型類似,通過信號驅動與通知完成數據準備就緒的判別,同時此過程非阻塞,這是和復用模型的區別
-
異步IO模型
異步IO模型.png
該模型與信號驅動類似,觸發內核某個操作,數據準備就緒,復制完成后,通知應用程序,與信號驅動的區別在于是否已經完成數據復制后通知
-
BIO
-
同步
BIO同步通信模型.png
由獨立的Acceptor線程負責監聽客戶端連接,然后為每個連接創建新的線程,處理完成后返回給客戶端,最大的問題就是缺乏彈性伸縮能力,當客戶并突增后,服務器線程膨脹,性能會下降,線程堆棧溢出、新建線程失敗等問題,最后導致系統宕機。
-
偽異步
BIO通信模型(偽異步).png
通過將客戶端Socket封裝成Task,投遞到線程池,通過線程對Socket做統一控制,解決了線程突增問題,但是由于底層通信還是同步阻塞模型,沒有從根本解決問題,當所有可用線程被阻塞,后續Socket進入隊列,隊列滿后,新請求甚至被直接拒絕掉,大量響應超時,導致客戶端認為服務器癱瘓
-
-
NIO
NIO通訊模型.pngNIO有三大核心類,Buffer、Channel、Selector,Buffer與Channel一一對應,Channel與Selector是多對一。Channel注冊到Selector,Selector輪詢就緒的Key,根據事件在Channel間切換,異步讀取消息到Buffer,通過解碼處理,最后可以發送消息Buffer到SocketChannel,通過Selector事件,通知事件注冊Key處理消息,Socket連接以及讀寫都是異步的,同時Selector通過epool實現,所以非常適合做高性能、高負載的網絡服務器
AIO
使用Proactor模式,通過Future、CompletionHandler實現異步操作結果以及通知的實現,不需要通過多路復用即可實現異步讀寫,從而簡化編程模型,由于不是本文主要內容,所以不仔細分析-
零拷貝
從操作系統層面講,數據不經過CPU拷貝從一個內從區域到另一個內存區域,內核緩沖區沒有產生重復數據-
傳統IO
傳統IO拷貝.png經過四次內存拷貝,四次上下文形態的切換
- JVM發起read,OS切換到內核態(第一次狀態切換),數據從Hardware通過DMA拷貝到Kernel(第一次拷貝)
- OS切換到用戶態(第二次狀態切換),將數據從kernel通過CPU拷貝到User(第二次拷貝)
- JVM發write,OS切換形態到內核態(第三次狀態切換),將數據拷貝到Kernel(第三次拷貝),然后切換到用戶態(第四次狀態切換)數據拷貝到Hardware(第四次拷貝)
-
Mmap優化
MMAP-IO拷貝.png三次內存拷貝,四次上下文形態的切換
整個數據流與傳統IO相似,區別在于節省了第二次拷貝,內核緩沖數據可以與用戶共享。 -
SendFile
SendFile-IO拷貝 -.png帶有DMA收集拷貝功能的SendFile是倆次拷貝,倆次內核的切換
- OS切換內核態,Hardware拷貝數據到Kernel
- Kernel拷貝Length、Offset到 Kernel(不計做數據拷貝)
- 傳統I/O用戶空間緩存了數據,所以應用程序可以對數據進行修改
- 通過Length、Offset直接將Kernel數據拷貝到協議引擎
- Java NIO中transferTo實現了該功能
-
二、Netty概述
Netty是在NIO問題眾多的情況下應聲而來,基于NIO提供了異步非阻塞、事件驅動的網絡應用程序框架與工具
- NIO存在的問題
- 類庫、API復雜,依賴額技能:多線程
- 可靠性能力的補齊,工作量和難度都很大:斷連重連、網絡閃斷、讀寫半包、失敗緩存、網絡擁堵、異常碼流的處理
- Netty應聲而來
1. API簡單、開發門檻低
2. 功能強大,預置多種編解碼,支持多種主流協議
3. 定制能力強,通過ChannelHandler,對通信框架靈活擴展
3. 性能高、成熟、穩定,社區活躍
三、Netty架構設計
-
架構模型
Netty邏輯機構圖.png
整體采用三層網絡架構進行設計開發,實現了NIO架構各層之間的解耦,便于上層協議棧的開發與邏輯的定制;Reactor層作為通信調度,PipeLine層攔截監聽事件,Service ChannelHandler 用于擴展與業務邏輯的編排,下面詳細介紹各層職責
- Reactor通信調度
主要負責網絡的連接以及讀寫操作,將網絡層的數據讀取到內存緩存區中,然后觸發各種網絡事件,例如:連接創建、連接激活、讀寫事件等,同時將事件回調到Pipeline中,由PipeLine做后續處理 - PipeLine職責鏈
復制事件在職責鏈中的有序傳播,同時負責動動態的編排,選擇監聽和處理自己感興趣的事件,可以攔截處理與向前\后傳播事件,通常不同Handler節點功能不同,例如編解碼Handler,負責消息協議轉化,這樣上層只需要關心業務邏輯,不需要感知底層的協議、線程模型的差異,實現了架構的分層隔離 - Service Handler 業務處理
可以分為倆類,一類是純粹的業務邏輯處理,另一類就是其他應用層的協議插件,用于特定協議相關的會話和鏈路管理
- Reactor通信調度
-
服務端 & 客戶端 創建過程時序
-
服務端
服務端順序圖.png -
客戶端
客戶端順序圖圖.png
-
-
線程控制
由于Netty采用異步通信模型,一個IO線程并發處理多個客戶端連接讀寫,IO多路復用 + 池化技術其實就是Reactor的基本設計思想,Reactor單獨運行在一個線程,負責監聽與分發事件,分發給適當的程序來處理IO事件,通過Handler實際處理相關事件,那么線程模型到底是什么樣,下面詳細講解-
單Reactor單線程模型
Reactor單線程.png- 典型的IO多路復用通過一個線程阻塞多路連接請求,Reactor通過select監聽客戶端事件,收到后通過Dispatch進行分發事件;
- 如果是連接事件,交由Acceptor進行連接處理,創建Handler完成后續處理;
- 如果非連接請求,分發到相應的handler處理,完成read -> 業務處理 - > send 過程處理
優點:模型簡單,無多線程,不存在進程通訊、資源競爭問題
缺點:性能問題:單線程無法發揮多核優勢,同時處理Handler業務會阻塞線程,導致客戶端連接超時,往往超時會重試,加重服務端壓力,惡性循環;可靠性問題:線程意外停止或進入死循環會導致整個系統不可用
使用于客戶端數據量有限,業務響應迅速的應用 -
單Reactor多線程模型
Reactor多線程 .png- Reactor啟動單線程監聽客戶端事件,通過Dispatch分發事件
- 連接事件交由Acceptor進行連接處理,創建Handler完成后續處理
- 非連接事件,由Reactor分發到相應的Handler處理
- handler只負責響應事件,read讀取數據后,分發到worker線程池中做業務處理
優點:發揮多核的處理能力
缺點:單Reactor線程處理所有監聽事件,在高并發情況下會成為瓶頸,同時多線程共享和訪問數據比較復雜 -
Reactor主從多線程模型
Reactor主從多線程.png- Reactor主線程通過select監聽連接事件,收到連接通知Acceptor處理連接事件
- 連接建立后主線程分配給Reactor子線程,創建Handler進行各種事件處理
- 有事件發生,SubReactor調用相應的Handler,handler通過read后將數據分發給worker線程去處理
- 一個MainReactor可以對應多個SubReactor
優點:主從線程數據交互簡單,職責明確,主線程負責接收連接,從線程負責后續業務處理,同時無需返回
缺點:編程難度較大-
Netty線程模型
Netty線程模型.png
該模式可以通過參數配置,支持以上三種Reactor線程模型
- 抽象出倆個線程池,Boss Group 負責客戶端連接請求,Worker Group負責網絡的讀寫,二者皆為NioEventLoopGroup,表示事件循環組,每一循環為NioEventLoop
- NioEventLoop表示一個不斷循環處理任務的線程,內置Selector監聽綁定在其上面的Socket通訊事件
- 每個Boss NioEventLoop包含三個步驟
- 輪詢監聽accept事件
- 處理accept事件,與client建立連接,生成NioSocketChannel,并將其注冊到Worker NioEventLoop中的Selector
- 處理任務隊列任務
- 每個Worker NioEventLoop循環執行步驟
- 輪詢監聽read、write事件
- 在對應的NioSocketChannel處理read、write事件
- 處理任務隊列任務
- 每個Worker NioEventLoop處理業務會通過PipeLine,其中內置了很多處理器,支持業務處理
-
-
Handler調用機制
Pipeline事件攔截和處理流程.png
在Netty架構設計中,Handler充當了處理入棧和出棧的數據處理邏輯的容器,實現相應接口(ChannelInBoundHandler)重寫相應方法就可以接收入棧事件和數據,將數據進行邏輯處理;向客戶端發送響應時,可用從InBound中沖刷數據,ChannelOutBoundHandler原理相同,處理出棧數據;其中入棧是相對PipeLine來說的,PipeLine中以LinkedList線程存儲Handler,從Socket中read數據到Pipeline進行處理稱為入棧,Handler執行順序為HeadHandler -> TailHandler,反之write到Socket稱為出棧,執行順序TailHandler - > HeadHandler
- 編解碼與協議開發
當Netty接收和發送消息時,就會發生一次數據轉換,根據入棧和出棧分別進行解碼和編碼,Netty自身提供了一系列編解碼器,都會實現ChannelInBoundHandler、ChannelOutBoundHandler接口并在channelRead中接收數據,進行數據的編解碼,數據處理后會轉發到寫一個Handler;由于TCP是 “流” 協議,也就是傳輸的數據是沒有界限的,所以導致在業務層面講不能確定每次發送是一條完整數據,這就是TPC粘包、拆包問題- TCP粘包、拆包問題
底層TCP無法理解上層業務數據,所以導致底層無法保證數據包不被拆分和重組,所以這個問題只能通過上層協議棧設計去解決,根據業界與主流協議的解決方案,可以總結為以下幾種- 消息定長
- 在包尾添加特殊字符進行分割,比如:回車
- 將消息分為消息頭、消息體,消息頭保存消息長度
- 更復雜的應用層協議
- 消息序列化
當進行遠程跨進程調用時,需要將被傳輸的Java對象編碼為字節數組或ByteBuffer對象,目標系統需要將接收到的數據進行相應解碼為Java對象,其中最常見的就是Java的序列化,實現Serializable接口Java序列化
序列化目的:網絡傳輸(本文重點)、對象持久化;
序列化是Java編碼中的一種,由于其種種缺陷,衍生出多種編
解碼技術與框架
缺陷:無法跨語言、序列化后的體積較大、性能低Protobuf
是一種與語言無關、平臺無關、性能高、擴展性好的數據序列化方法,可以用于數據傳輸協議以及數據存儲,可類比于XML但是比XML更小、更快、更簡單,通過數據描述文件和代碼生成機制實現以上特點-
Thrift
Thrift是一種接口描述語言和二進制通訊協議,它被用來定義和創建跨語言的服務。它被當作一個遠程過程調用(RPC)框架來使用,是由Facebook為“大規模跨語言服務開發”而開發的。——來源:百度百科
-
Mershalling
是一個Java對象序列話的API包,修正了JDK自帶序列化的諸多問題,在兼容Serializable的同時,新增可調參數與附加特性- 可插拔的類解析器,提供更加便捷的定制策略,
- 可插拔的對象替換技術
- 無需實現指定接口
- 通過緩沖提高性能
- TCP粘包、拆包問題
- 如何達到架構質量指標
-
高性能
性能是設計出來的,而不是測試出來的
影響網絡通信性能多,主要從傳輸、協議、進程三個方面看,選取怎么樣內存模型、采用怎么樣的通信協議、線程模型如何選擇,下面詳細介紹Netty在這些方面是如何做的
- 采用異步非阻塞的IO類庫,基于Reactor模式實現,平滑的處理客戶端線性增長;通過NioEventLoop聚合異步多路復用Selector,并發處理成百上千個SocketChannel的讀寫,極大提升性能、彈性伸縮能力;
- 網絡傳輸緩沖區使用 直接內存,避免內存復制,提高IO讀寫性能;
通過三種方面體現零拷貝:- Netty的讀寫采用DirectBuffer堆外內存直接內存,相比于堆內存要講堆內存緩存區數據拷貝到直接內存, 不需要進行字節緩沖區的二次拷貝
- 通過實現CompositeByteBuf,將多個ByteBuf分裝成一個ByteBuf,對外提供統一ByteBuf接口,實際就是一個裝飾器,將多個ByteBuf組合成一個集合,這樣避免了內存拷貝;
- 文件傳輸中通過transferTo方法直接文件發送到目標Channel中, 不需要進行循環拷貝(底層SendFile)
- 支持通過內存池的方式循環利用ByteBuffer,避免頻繁創建、銷毀Buffer對象造成的性能開銷;雖然JVM虛擬機與JIT及時編輯編譯的發展,對象的分配和回收是個非常輕量級的工作,但是對于緩沖區的情況稍有不同,緩沖區操作頻繁,同時還是堆外內存,這樣分配和回收對象就會稍有耗時。為了盡量重用緩沖區,Netty提供了基于內存池的緩沖區重用機制,通過啟動輔助類中配置相關參數,事項差異化定制;
PooledByteBufAllocator.DEFAULT.directBuffer(1024)
- 可配置的IO線程數、TCP參數等,為不同的應用場景提供定制化的調優參數;通過輔助類的參數配置,可以使Nettry支持任意一種Reactor線程模型,支持不同的業務場景,同時合理地設置TCP參數滿足不同的用戶場景
- 采用環形數組緩沖區實現無鎖化并發編程,替代傳統的線程安全容器以及鎖;在數據讀寫時,采用循環數數組緩沖區如ChannelOutBoundBuffer,每次寫入通道,然后彈出,直到無內容
- 合理地使用線程安全容器、原子類等,提高系統的并發處理能力;volatile的大量、正確使用;CAS和原子類的廣泛使用;線程安全容器的使用;讀寫鎖提高并發性能
- 關鍵資源使用單線程串行化,避免多線程并發訪問帶來的鎖競爭和額外的CPU資源消耗問題;表面上串行化設計似乎CPU利用率不高,并發程度不夠,但是通過Worker NioEventLoop的線程池的參數,可以同時啟動多個串行化的線程并行運行,這種局部無鎖化的穿串行線程設計相比一個隊列,多個工作線程性能更優
- 通過引用計數器及時的釋放不在應用的對象,細粒度的內存管理降低GC的頻率,
AbstractReferenceCountedByteBuf
-
可靠性
作為高性能的異步通訊框架,架構的可靠性是重要硬性指標- 鏈路有效性檢測
基于長連接帶來諸多便利,但鏈路的有效性沒有保證,Netty可以通過心跳周期性的檢測,在系統空閑無業務時可以識別網絡閃斷、網絡單通等網絡異常進行自動關閉、重連,使系統空閑與高峰能順利過度;同時業務消息可以充當鏈路檢測,心跳只需要系統空閑時發送;
心跳機制:Ping - Pong 、Ping - Ping
Netty提供相關類庫:io.netty.handler.timout.*
,進行空閑檢測,主要監聽讀空閑、寫空閑、讀寫空閑事件,自定義邏輯代碼實現協議層的心跳檢測 - 內存保護機制
通過對象引用計數器對Bytebuf等內置對象進行細粒度的內存申請與釋放,對非法的對象引用做檢測與保護,通過內存池對ByteBuf重用、分配機制(預分配、協議定長)、設置內充容量上限(鏈路總數、單個緩沖區大小、消息長度) - 優雅停機
基于JVM發送退出信號量,Netty對所有涉及資源釋放、回收的地方,都采用了優雅退出的方式,也就是退出前釋放相關模塊資源占用、將緩沖區的消息處理完成、待刷新數據持久化到硬盤或數據庫,所有處理執行完畢后退出
- 鏈路有效性檢測
可定制
主要體現在Pipelin的職責鏈設計,便于業務邏輯的定制化;基于接口開發;提供大量工廠類、配置化輔助類,可以按照業務需求進行配置、創建對象;可擴展
很方便的進行應用層協議定制擴展
-
四、核心組件工作原理及源碼解析
以下所有源碼分析基于Netty 4.1版本
-
Buffer(以ByteBuffer為主)
- 工作原理
- NIO原生ByteBuffer
- 核心屬性
// Invariants: mark <= position <= limit <= capacity private int mark = -1; -- 標記 private int position = 0; -- 位置,操作指針 private int limit; -- 操作極限點 private int capacity; -- 容量
- 核心方法
public final int capacity( ) --返回此緩沖區的容量 public final int position( ) --返回此緩沖區的位置 public final Buffer mark( ) --在此緩沖區的位置設置標記 public final Buffer reset( ) --將此緩沖區的位置重置為以前標記的位置 public final Buffer clear( ) --清除此緩沖區, 即將各個標記恢復到初始值 public final Buffer flip( ) --反轉此緩沖區 public final Buffer rewind( ) --重繞此緩沖區 public final int remaining( ) --返回當前位置與限制之間的元素數 public final boolean hasRemaining( ) --當前位置和限制之間是否有元素 public abstract boolean isReadOnly( ) --是否為只讀緩沖區 public abstract boolean hasArray(); --是否具有可訪問的底層實現數組 public abstract Object array(); --底層實現數組 public abstract int arrayOffset(); --底層實現數組中第一個元素的偏移量 public abstract boolean isDirect(); --是否為直接緩沖區 public static ByteBuffer allocateDirect(int capacity) --創建直接緩沖區 public static ByteBuffer allocate(int capacity) --創建堆內存緩存區 public abstract byte get( ); --position位置開始向后遍歷 public abstract ByteBuffer put (byte b); --position位置追加
- 核心屬性
- JDK原生
java.nio.ByteBuffer
中長度固定,由于只有一個位置標識指針,每次讀寫需要flip()等操作進行指正切換,API有限,高級功能需要自己去實現;基于以上問題,Netty提供了自己的Buffer實現,主要采用倆中策略 ①參考JDK實現,處理缺陷、擴展功能;②采用Facade模式,聚合并包裝JDK原生Buffer -
ByteBuf通過readerIndex & writerIndex倆個指針完成緩沖區的讀寫避免flip,通過擴容解決定容問題
ByteBuf.png - Netty中ByteBuf核心屬性 & 方法
- 屬性
static final ResourceLeakDetector<ByteBuf> leakDetector =ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class); --記錄處理內存泄露Buf /**0 <= readerIndex <= writerIndex <= maxCapacity**/ int readerIndex; --讀索引 int writerIndex; --寫索引 private int markedReaderIndex; --du索引標記 private int markedWriterIndex; --寫索引標記 private int maxCapacity; --最大容量
- 方法
public byte readByte()--讀 public ByteBuf writeByte() --寫 public ByteBuf discardReadBytes() --釋放discard區域數據(重用) public ByteBuf clear() --讀寫索引復位 public ByteBuf markXXX() -- 標記 public ByteBuf resetXXX() -- 復位標記 public ByteBuf slice() --可讀緩存區
- 屬性
- 內存池原理
為了集中管理內存,預先分配一大塊連續內存的分配與釋放,同時由于頻繁調用系統來申請內存從而提高性能大大提升,Netty中PoolArena是由多個Chunk組成的大塊內存區域,每個Chunk是由多個Page組成,PoolChunk主要用來組織管理多個Page的內存分配和釋放,其中的Page被構建成二叉樹,內存選擇是對樹的深度優先遍歷,但是同層隨機選擇,如果分配內存小于page,PoolSubpage會分成相等的塊,分多大是由第一次申請決定的,后面所有的申請都按這個來,無論是Chunk還是Page都是通過狀態位來標識是否可用 - 相關類
- ByteBufHolder:對ByteBuf的一層封裝,便于協議攜帶數據在消息體中
- ByteBufAllocator:Buf分配器,上面源碼涉及到,主要分池與非池的Buf的分配;
- ByteBufUtil : ByteBuf工具類
- NIO原生ByteBuffer
- 源碼解析
-
繼承關系
Diagram-ByteBuf.png以上是ByteBuf主要功能類圖,從內存分配來看,分為堆內存、直接內存,二者的區別前面已經提過,無非就是分配&回收、復制方面各有利弊,所以讀寫緩沖使用直接內存,編解碼使用堆內存,使系能到達最優;從內存的回收來看,可以分為池化與普通Buf,池化可以重用內存,提升效率,降低GC消耗,但是在維護管理更加復雜;Netty提供以上策略,共開發者選擇。
-
讀取Buf(AbstractByteBuf.java)
public byte readByte() { checkReadableBytes0(1); //校驗是否可讀(一個字節) int i = readerIndex; //從當前可讀位置開始 byte b = _getByte(i);// 由于子類不同的技術實現,所以由子類實現 readerIndex = i + 1; //位置后移 return b; } private void checkReadableBytes0(int minimumReadableBytes) { ensureAccessible(); //訪問確認 if (checkBounds && readerIndex > writerIndex - minimumReadableBytes) {//是否存在可讀數據() throw new IndexOutOfBoundsException(String.format( "readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s", readerIndex, minimumReadableBytes, writerIndex, this)); } } /** * release前都要校驗 * 是否可訪問 * isAccessible() --return refCnt() != 0 //是否存在引用 * 這里對refCnt做一個說明:初始化對象時會置為1,release會置為0, * 當對象不可達后,由于JVM在不知道Netty后續操作,所以可能會釋放掉對象, * 這樣release就沒法執行,沒有歸還資源到內存池中,導致后續內存泄漏, * 這里會使用leakDetector 解決該問題 */ protected final void ensureAccessible() { if (checkAccessible && !isAccessible()) { //checkAccessible 配置項,() : throw new IllegalReferenceCountException(0); } }
-
寫Buf(AbstractByteBuf.java)
public ByteBuf writeByte(int value) { ensureWritable0(1); _setByte(writerIndex++, value); return this; } final void ensureWritable0(int minWritableBytes) { final int writerIndex = writerIndex(); final int targetCapacity = writerIndex + minWritableBytes; //寫入所需容量 if (targetCapacity <= capacity()) {//小于當前容量 ensureAccessible(); return; //允許寫入 } if (checkBounds && targetCapacity > maxCapacity) { //目標所需容量大于最大容量 拋出OutOfBoundException ensureAccessible(); throw new IndexOutOfBoundsException(String.format( "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", writerIndex, minWritableBytes, maxCapacity, this)); } // Normalize the target capacity to the power of 2. final int fastWritable = maxFastWritableBytes();//允許寫入字節數(多此一舉) //計算擴容后容量 int newCapacity = fastWritable >= minWritableBytes ? writerIndex + fastWritable : alloc().calculateNewCapacity(targetCapacity, maxCapacity); // Adjust to the new capacity. //擴容后需要新建緩沖區,復制不同子類實現不同, capacity(newCapacity); } /** * AbstractByteBufAllocator.java * 計算擴容后容量 * minNewCapacity 容量下限 * maxCapacity 容量上限 */ public int calculateNewCapacity(int minNewCapacity, int maxCapacity) { // 參數合法性校驗 checkPositiveOrZero(minNewCapacity, "minNewCapacity"); if (minNewCapacity > maxCapacity) { throw new IllegalArgumentException(String.format( "minNewCapacity: %d (expected: not greater than maxCapacity(%d)", minNewCapacity, maxCapacity)); } // 設置閾值 4MB final int threshold = CALCULATE_THRESHOLD; // 4 MiB page //如果是 擴容為閾值直接返回閾值 if (minNewCapacity == threshold) { return threshold; } // If over threshold, do not double but just increase by threshold. // 擴容超過閾值,不翻倍,而是步進閾值(增加閾值的倍數) if (minNewCapacity > threshold) { int newCapacity = minNewCapacity / threshold * threshold; if (newCapacity > maxCapacity - threshold) { newCapacity = maxCapacity; } else { newCapacity += threshold; } return newCapacity; } // Not over threshold. Double up to 4 MiB, starting from 64. // 小于閾值,倍增(初始值64 ->64 *2 -> 64*2*2) int newCapacity = 64; while (newCapacity < minNewCapacity) { newCapacity <<= 1; } // 不超過預設的最大容量 return Math.min(newCapacity, maxCapacity); }
-
重用緩沖區(discardReadBytes)
public ByteBuf discardReadBytes() { //無可重用 if (readerIndex == 0) { ensureAccessible(); return this; } //讀寫位置不同(暗含讀位置>0) if (readerIndex != writerIndex) { // 復制數組,未讀取的數據復制到其實位置 setBytes(0, this, readerIndex, writerIndex - readerIndex); //重置讀寫位置 writerIndex -= readerIndex; adjustMarkers(readerIndex); readerIndex = 0; } else { //讀寫位置相等,無可讀數據,不需要復制,直接位置置0即可重用 ensureAccessible(); adjustMarkers(readerIndex); writerIndex = readerIndex = 0; } return this; }
-
對引用計數進行分析(AbstractReferenceCountedByteBuf.java)
// 顳部通過原子(AtomicIntegerFieldUpdater)的方式對成員變量進行操作 private static final ReferenceCountUpdater<AbstractReferenceCountedByteBuf> updater = new ReferenceCountUpdater<AbstractReferenceCountedByteBuf>() { // 追蹤對象引用次數 private volatile int refCnt = updater.initialValue(); //引用計數器 +1 public ByteBuf retain() { return updater.retain(this);// 最終CAS處理updater().getAndAdd(instance, -rawIncrement); } // 引用計數器 -1 public boolean release(int decrement) { return handleRelease(updater.release(this, decrement));//最終調用 updater().compareAndSet(instance, expectRawCnt, 1) }
-
非池化堆內存Buf(UnpooledHeapByteBuf)
頻繁內存分配&回收會對性能造成一定影響,但是申請和釋放的成本會低一些private final ByteBufAllocator alloc; --內存分配器 byte[] array; -- 緩沖數據(這里可以使用Nio中Buffer替代,這里是為了提升性能和更加便捷的進行位操作,) private ByteBuffer tmpNioBuf; --實現與JDK原生Buffer的轉換 // 這里對AbstractByteBuf中要求子類實現的方法進行講解 // get & set 就是簡單的賦值 protected byte _getByte(int index) { return HeapByteBufUtil.getByte(array, index); } protected void _setByte(int index, int value) { HeapByteBufUtil.setByte(array, index, value); } //擴容 public ByteBuf capacity(int newCapacity) { //參數校驗 checkNewCapacity(newCapacity); byte[] oldArray = array; int oldCapacity = oldArray.length; if (newCapacity == oldCapacity) {//無修改 return this; } //拷貝的末尾下標 int bytesToCopy; if (newCapacity > oldCapacity) { bytesToCopy = oldCapacity; } else { //如果是縮容,需要截取到新容量位置,調整readerIndex & writerIndex trimIndicesToCapacity(newCapacity); bytesToCopy = newCapacity; } // 創建新數組 byte[] newArray = allocateArray(newCapacity); //拷貝數據到新數組 System.arraycopy(oldArray, 0, newArray, 0, bytesToCopy); //替換關聯數組 setArray(newArray); //釋放舊數組(NOOP) freeArray(oldArray); return this; } //ByteBuf轉原生ByteBuffer public ByteBuffer nioBuffer(int index, int length) { ensureAccessible(); return ByteBuffer.wrap(array, index, length).slice(); } //數組轉為ByteBuffer public static ByteBuffer wrap(byte[] array, int offset, int length) { try { //將數組轉為ByteBuffer return new HeapByteBuffer(array, offset, length); } catch (IllegalArgumentException x) { throw new IndexOutOfBoundsException(); } }
UnpooledDirectByteBuf與此類類似,將array換位ByteBuffer,分配實現為:
ByteBuffer.allocateDirect(initialCapacity)
-
池化Buf如何復用
PooledByteBufAllocator //創建堆外Buffer protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) { //獲取線程線程綁定的池緩沖 PoolThreadCache cache = (PoolThreadCache)this.threadCache.get(); // 通過緩沖獲取到池域 PoolArena<ByteBuffer> directArena = cache.directArena; Object buf; if (directArena != null) { // 分配內存 buf = directArena.allocate(cache, initialCapacity, maxCapacity); } else { buf = PlatformDependent.hasUnsafe() ? UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) : new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity); } return toLeakAwareBuffer((ByteBuf)buf); } PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) { //創建緩存 PooledByteBuf<T> buf = newByteBuf(maxCapacity); //分配內存 allocate(cache, buf, reqCapacity); return buf; } //創建ByteBuf protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) { //是否支持un_safe if (HAS_UNSAFE) { return PooledUnsafeDirectByteBuf.newInstance(maxCapacity); } else { return PooledDirectByteBuf.newInstance(maxCapacity); } } // 創建Buf實例 static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) { //采用對象線程池(先復用不行再創建) PooledUnsafeDirectByteBuf buf = RECYCLER.get(); buf.reuse(maxCapacity); return buf; } public final T get() { if (maxCapacityPerThread == 0) { return newObject((Handle<T>) NOOP_HANDLE); } //獲取線程綁定棧 Stack<T> stack = threadLocal.get(); //獲取對象句柄 DefaultHandle<T> handle = stack.pop(); if (handle == null) { //無法獲取,創建對象 handle = stack.newHandle(); handle.value = newObject(handle); } return (T) handle.value; } //復用重置相關參數 final void reuse(int maxCapacity) { maxCapacity(maxCapacity); resetRefCnt(); setIndex0(0, 0); discardMarks(); } /** *分配內存 * 主要實現以下邏輯 * 每個Chunk是由多個Page組成,PoolChunk主要用來組織管理多個Page的內存分配和釋放, * 其中的Page被構建成二叉樹,內存選擇是對樹的深度優先遍歷,但是同層隨機選擇, * 如果分配內存小于page,PoolSubpage會分成相等的塊,分多大是由第一次申請決定的, * 后面所有的申請都按這個來,無論是Chunk還是Page都是通過狀態位來標識是否可用, */ private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) { final int normCapacity = normalizeCapacity(reqCapacity); if (isTinyOrSmall(normCapacity)) { // capacity < pageSize int tableIdx; PoolSubpage<T>[] table; boolean tiny = isTiny(normCapacity); if (tiny) { // < 512 if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) { // was able to allocate out of the cache so move on return; } tableIdx = tinyIdx(normCapacity); table = tinySubpagePools; } else { if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) { // was able to allocate out of the cache so move on return; } tableIdx = smallIdx(normCapacity); table = smallSubpagePools; } final PoolSubpage<T> head = table[tableIdx]; /** * Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and * {@link PoolChunk#free(long)} may modify the doubly linked list as well. */ synchronized (head) { final PoolSubpage<T> s = head.next; if (s != head) { assert s.doNotDestroy && s.elemSize == normCapacity; long handle = s.allocate(); assert handle >= 0; s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity); incTinySmallAllocation(tiny); return; } } synchronized (this) { allocateNormal(buf, reqCapacity, normCapacity); } incTinySmallAllocation(tiny); return; } if (normCapacity <= chunkSize) { if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) { // was able to allocate out of the cache so move on return; } synchronized (this) { allocateNormal(buf, reqCapacity, normCapacity); ++allocationsNormal; } } else { // Huge allocations are never served via the cache so just call allocateHuge allocateHuge(buf, reqCapacity); } }
-
- 工作原理
-
Channel
- 工作原理
- 主要負責客戶端的連接、網絡的讀寫,鏈路的關閉、一些框架功能等,在原生Channel諸多不便的情況下,Netty重新設計
- 采用Facade封裝、功能齊全、聚合原生Channel統一分配調度,
- 核心方法
io.netty.channel.Channel#alloc --獲取Buf分配器 io.netty.channel.Channel#config --配置信息 io.netty.channel.Channel#eventLoop --獲取eventLoop io.netty.channel.Channel#flush --將Buf中的寫入Channel io.netty.channel.Channel#isActive --是否激活 io.netty.channel.Channel#isOpen --是否打開 io.netty.channel.Channel#isRegistered --是否注冊到EventLoop io.netty.channel.Channel#isWritable --是否可寫 io.netty.channel.Channel#localAddress --本地地址 io.netty.channel.Channel#metadata --元數據 io.netty.channel.Channel#pipeline --獲取pipeLine io.netty.channel.Channel#read --讀取到Buf io.netty.channel.Channel#remoteAddress --遠端地址 io.netty.channel.ChannelOutboundInvoker#bind(java.net.SocketAddress) --綁定本地Socket地址 io.netty.channel.ChannelOutboundInvoker#connect(java.net.SocketAddress) --連接服務器地址 io.netty.channel.ChannelOutboundInvoker#deregister() --注銷 io.netty.channel.ChannelOutboundInvoker#disconnect() --斷開連接 io.netty.channel.ChannelOutboundInvoker#writeAndFlush(java.lang.Object) --寫入channel
- 源碼解析
-
繼承關系
Diagram-Channel.png -
抽象統一處理的核心
- 屬性
//AbstractChannel private final Channel parent; --父Channel private final Unsafe unsafe; private final DefaultChannelPipeline pipeline; --所屬Pipeline private final CloseFuture closeFuture = new CloseFuture(this); private volatile SocketAddress localAddress; private volatile SocketAddress remoteAddress; private volatile EventLoop eventLoop; --所注冊的EventLoop // AbstractNioChannel.java private final SelectableChannel ch; --server 、client的統一父類 protected final int readInterestOp; --OP_READ事件 volatile SelectionKey selectionKey; --注冊到Selector中的Key(volatile 多線程可見) //AbstractNioByteChannel //繼續寫半包消息 private final Runnable flushTask = new Runnable() { public void run() { // Calling flush0 directly to ensure we not try to flush messages that were added via write(...) in the // meantime. ((AbstractNioUnsafe) unsafe()).flush0(); } };
- 方法
//AbstractChannel //基本操作都是委托到Pipeline處理 public ChannelFuture bind(SocketAddress localAddress) { return pipeline.bind(localAddress); } public ChannelFuture connect(SocketAddress remoteAddress) { return pipeline.connect(remoteAddress); } public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { return pipeline.connect(remoteAddress, localAddress); } public ChannelFuture disconnect() { return pipeline.disconnect(); } public ChannelFuture close() { return pipeline.close(); } // AbstractNioChannel.java //當前Channel注冊到EventLoop protected void doRegister() throws Exception { boolean selected = false; //注冊標識 for (;;) { try { selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); //將當前channel注冊到eventLoop中的Selector ,其中0標識對任何事件不感興趣 return; } catch (CancelledKeyException e) {//連接異常(當前被取消) if (!selected) {//首次異常 // Force the Selector to select now as the "canceled" SelectionKey may still be // cached and not removed because no Select.select(..) operation was called yet. eventLoop().selectNow();//將失效的SelectorKey移除,繼續下次注冊 selected = true; } else { // We forced a select operation on the selector before but the SelectionKey is still cached // for whatever reason. JDK bug ? throw e; } } } } // 讀操作 protected void doBeginRead() throws Exception { // Channel.read() or ChannelHandlerContext.read() was called final SelectionKey selectionKey = this.selectionKey; if (!selectionKey.isValid()) { //是否有效 return; } readPending = true; //設置讀等待 final int interestOps = selectionKey.interestOps(); if ((interestOps & readInterestOp) == 0) { selectionKey.interestOps(interestOps | readInterestOp);//設置家監聽讀操作 } } //AbstractNioByteChannel protected void doWrite(ChannelOutboundBuffer in) throws Exception { int writeSpinCount = config().getWriteSpinCount();//寫操作最大循環次數,默認16, do { Object msg = in.current();//消息循環數組彈出消息 if (msg == null) {//無消息 所有消息發送完成 // Wrote all messages. clearOpWrite(); // 清楚半包標識 // Directly return here so incompleteWrite(...) is not called. return; } writeSpinCount -= doWriteInternal(in, msg);//這里執行寫,寫的過程可能發生TCP緩沖區已滿,導致空循環占用CPU資源,導致IO線程無法處理其他線程 } while (writeSpinCount > 0); incompleteWrite(writeSpinCount < 0);//根據循環是否到達極限,設置寫半包標識為true } private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception { if (msg instanceof ByteBuf) { //是否Byte類型 ByteBuf buf = (ByteBuf) msg; if (!buf.isReadable()) { //是否可讀 in.remove(); //不可讀移除消息 return 0; } final int localFlushedAmount = doWriteBytes(buf); //進行消息發送 if (localFlushedAmount > 0) { in.progress(localFlushedAmount); if (!buf.isReadable()) { in.remove(); } return 1;// 本次沒有發送為0字節(TCP緩沖區已滿,可發送循環次數 -1) } } } //設置是否寫完全(寫半包處理) protected final void incompleteWrite(boolean setOpWrite) { / / Did not write completely. if (setOpWrite) {//是否寫完全 setOpWrite();//未寫完全,后續Selector會繼續輪詢處理為發送完的消息 } else { // It is possible that we have set the write OP, woken up by NIO because the socket is writable, and then // use our write quantum. In this case we no longer want to set the write OP because the socket is still // writable (as far as we know). We will find out next time we attempt to write if the socket is writable // and set the write OP if necessary. clearOpWrite();//清除寫半包標識 // Schedule flush again later so other tasks can be picked up in the meantime //啟動任務,后續寫操作 eventLoop().execute(flushTask); } } //寫不完全,設置標識 protected final void setOpWrite() { final SelectionKey key = selectionKey(); // Check first if the key is still valid as it may be canceled as part of the deregistration // from the EventLoop // See https://github.com/netty/netty/issues/2104 if (!key.isValid()) {//是否有效 return; } final int interestOps = key.interestOps(); if ((interestOps & SelectionKey.OP_WRITE) == 0) { key.interestOps(interestOps | SelectionKey.OP_WRITE); } }
- 屬性
-
NioServerSocketChannel.java
//創建ServerSocketChannel private static ServerSocketChannel newSocket(SelectorProvider provider) { try { return provider.openServerSocketChannel(); } catch (IOException e) { throw new ChannelException( "Failed to open a server socket.", e); } } //基本所有的基礎操作都依賴于原生ServerSocketChannel protected void doBind(SocketAddress localAddress) throws Exception { if (PlatformDependent.javaVersion() >= 7) { javaChannel().bind(localAddress, config.getBacklog());// } else { javaChannel().socket().bind(localAddress, config.getBacklog()); } } protected int doReadMessages(List<Object> buf) throws Exception { //等待客戶端連接 SocketChannel ch = SocketUtils.accept(javaChannel()); try { if (ch != null) { //創建客戶端SocketChannel buf.add(new NioSocketChannel(this, ch)); return 1; } } catch (Throwable t) { ········ } return 0; }
-
NioSocketChannel.java
//連接服務 protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { if (localAddress != null) { doBind0(localAddress);//綁定地址 } boolean success = false; try { boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);//連接服務 if (!connected) { selectionKey().interestOps(SelectionKey.OP_CONNECT);//沒有立即連接成功,注冊連接事件 } success = true; return connected; } finally { if (!success) { doClose();//連接失敗,關閉連接 } } }
-
UnSafe(Channel的輔助接口)
Diagram-UnSafe.png//將Channel注冊到EventLoop中的Selector public final void register(EventLoop eventLoop, final ChannelPromise promise) { ObjectUtil.checkNotNull(eventLoop, "eventLoop"); if (isRegistered()) {//是否已經注冊過 promise.setFailure(new IllegalStateException("registered to an event loop already")); return; } if (!isCompatible(eventLoop)) {//是否是兼容Loop promise.setFailure( new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName())); return; } AbstractChannel.this.eventLoop = eventLoop; if (eventLoop.inEventLoop()) {//當前線程是否為EventLoop線程 register0(promise); //同一線程,無并發問題 } else { try { eventLoop.execute(new Runnable() {//放到任務隊列 @Override public void run() { register0(promise);// 注冊 } }); } catch (Throwable t) {//異常關閉 logger.warn( "Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, t); closeForcibly(); closeFuture.setClosed(); safeSetFailure(promise, t); } } } private void register0(ChannelPromise promise) { try { // check if the channel is still open as it could be closed in the mean time when the register // call was outside of the eventLoop if (!promise.setUncancellable() || !ensureOpen(promise)) { return; } boolean firstRegistration = neverRegistered;//是否首次注冊 doRegister(); //設置標記位 neverRegistered = false; registered = true; pipeline.invokeHandlerAddedIfNeeded();//回調pipeline中的handlerAdded()方法,通知注冊成功 safeSetSuccess(promise);//設置異步狀態 pipeline.fireChannelRegistered(); //回調pipeLine.fireChannelRegistered() if (isActive()) {//是否激活 if (firstRegistration) {//第一次激活 pipeline.fireChannelActive();//回調pipeLine.fireChannelActive() } else if (config().isAutoRead()) { // This channel was registered before and autoRead() is set. This means we need to begin read // again so that we process inbound data. // // See https://github.com/netty/netty/issues/4805 beginRead();//首次激活,開始讀 } } } catch (Throwable t) { // Close the channel directly to avoid FD leak. closeForcibly(); closeFuture.setClosed(); safeSetFailure(promise, t); } } //其他放方法不再贅述
-
- 工作原理
-
ChannelPipeline
- 工作原理
類似與過濾器組,通過職責鏈模式對用戶是的事件攔截處理,在前面我們已經大體介紹了原理。Netty中的事件分為inbound與outbound,- inbound事件方法:fireXXX()
- outbound事件方法:bind()、 connect()、 write()、 flush()、 read()、disconnect()、 close()
- 源碼解析
public final ChannelPipeline addBefore( EventExecutorGroup group, String baseName, String name, ChannelHandler handler) { final AbstractChannelHandlerContext newCtx; final AbstractChannelHandlerContext ctx; synchronized (this) {//保證線程安全 checkMultiplicity(handler);//如果handler不共享 & 在pipeLine中已經存在 name = filterName(name, handler); ctx = getContextOrDie(baseName);//更具名稱獲取HandlerContext newCtx = newContext(group, name, handler);//創建新HandlerContext addBefore0(ctx, newCtx);//雙向列表添加handler /** * newCtx.prev = ctx.prev; *newCtx.next = ctx; *ctx.prev.next = newCtx; *ctx.prev = newCtx; */ // If the registered is false it means that the channel was not registered on an eventLoop yet. // In this case we add the context to the pipeline and add a task that will call // ChannelHandler.handlerAdded(...) once the channel is registered. if (!registered) { newCtx.setAddPending(); callHandlerCallbackLater(newCtx, true); return this; } EventExecutor executor = newCtx.executor(); if (!executor.inEventLoop()) { callHandlerAddedInEventLoop(newCtx, executor); return this; } } callHandlerAdded0(newCtx); return this; } //事件是如何傳遞的(以fireChannelActive事件分析) public ChannelHandlerContext fireChannelRead(final Object msg) { invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg); return this; } //查找inBound相關ctx并返回(Handler的順序執行核心代碼) private AbstractChannelHandlerContext findContextInbound(int mask) { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.next; } while ((ctx.executionMask & mask) == 0);//是否Inbound相關實現,旨在尋找倆一個handler return ctx; } //執行channelRead方法 static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) { final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) {//是否EventLoop線程 next.invokeChannelRead(m);//直接調用 } else { executor.execute(new Runnable() {//新起Task @Override public void run() { next.invokeChannelRead(m); } }); } } private void invokeChannelRead(Object msg) { if (invokeHandler()) {//是否調用過該Handler try { ((ChannelInboundHandler) handler()).channelRead(this, msg);//調用過,傳遞事件 } catch (Throwable t) { notifyHandlerException(t); } } else { fireChannelRead(msg);//直接調用 } }
- 工作原理
-
ChannelHandler
- 工作原理
-
繼承關系
Diagram-ChannelHandler.png
-
- 源碼解析
主要分析ByteToMessageDecoderpublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof ByteBuf) {//是否ByteBuf CodecOutputList out = CodecOutputList.newInstance(); try { first = cumulation == null;//是否存在未解碼的消息 cumulation = cumulator.cumulate(ctx.alloc(), first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg);//根據是否半包消息,分配Buf callDecode(ctx, cumulation, out);//解碼 } catch (DecoderException e) { throw e; } catch (Exception e) { throw new DecoderException(e); } finally {//釋放資源 if (cumulation != null && !cumulation.isReadable()) { numReads = 0; cumulation.release(); cumulation = null; } else if (++ numReads >= discardAfterReads) { // We did enough reads already try to discard some bytes so we not risk to see a OOME. // See https://github.com/netty/netty/issues/4275 numReads = 0; discardSomeReadBytes(); } int size = out.size(); firedChannelRead |= out.insertSinceRecycled(); fireChannelRead(ctx, out, size); out.recycle(); } } else { ctx.fireChannelRead(msg); } } //解碼 protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { try { while (in.isReadable()) {//是否存在可讀 int outSize = out.size(); if (outSize > 0) {//如果存在解碼但未發送的消息 fireChannelRead(ctx, out, outSize);//將消息透傳出去 out.clear();//清空以解碼消息集合 // Check if this handler was removed before continuing with decoding. // If it was removed, it is not safe to continue to operate on the buffer. // // See: // - https://github.com/netty/netty/issues/4635 if (ctx.isRemoved()) {//當前ctx是否已經被移除 break; } outSize = 0; } int oldInputLength = in.readableBytes();//可讀字節數 decodeRemovalReentryProtection(ctx, in, out);//嘗試對何合并后的消息進行解碼 // Check if this handler was removed before continuing the loop. // If it was removed, it is not safe to continue to operate on the buffer. // // See https://github.com/netty/netty/issues/1664 if (ctx.isRemoved()) {//如果decodeRemovalReentryProtection嘗試解碼成功 break; } if (outSize == out.size()) {//如果沒有生成解碼消息,說明是半包 if (oldInputLength == in.readableBytes()) {//任然沒有新刻度消息,推出循環 break; } else { continue;//否者繼續讀 } } if (oldInputLength == in.readableBytes()) {//沒有讀取成功 throw new DecoderException( StringUtil.simpleClassName(getClass()) + ".decode() did not read anything but decoded a message."); } if (isSingleDecode()) {//是否是單條信息解碼器,如果是推出循環,解碼完成 break; } } } catch (DecoderException e) { throw e; } catch (Exception cause) { throw new DecoderException(cause); } }
- 工作原理
-
EventLoop
- 工作原理
-
繼承關系
Diagram-EventLoop.png 是Netty線程設計的完美體現,提升并發性能,很大程度避免鎖,局部無鎖話,前面已經分析了線程模型的原理,如何通過參數就可以在Reactor各個模型間完美切換等,這里不再贅述
-
- 源碼解析
private Selector selector; //selector的包裝對象(通過 **io.netty.noKeySetOptimization** 配置是否開啟,默認不開啟) private Selector unwrappedSelector; //原生Selector對象 private SelectedSelectionKeySet selectedKeys; private final SelectorProvider provider; //Selector提供者,通過SPI實現 //開啟Selector private SelectorTuple openSelector() { final Selector unwrappedSelector; try { unwrappedSelector = provider.openSelector();//設置原生Selector } catch (IOException e) { throw new ChannelException("failed to open a new selector", e); } if (DISABLE_KEY_SET_OPTIMIZATION) {//是否開啟Selector優化(io.netty.noKeySetOptimization) return new SelectorTuple(unwrappedSelector);//未開啟,直接返回 } //反射獲取Selector實現 Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { try { return Class.forName( "sun.nio.ch.SelectorImpl", false, PlatformDependent.getSystemClassLoader()); } catch (Throwable cause) { return cause; } } }); //是否為Class if (!(maybeSelectorImplClass instanceof Class) || // ensure the current selector implementation is what we can instrument. !((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) { if (maybeSelectorImplClass instanceof Throwable) { Throwable t = (Throwable) maybeSelectorImplClass; logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t); } return new SelectorTuple(unwrappedSelector); } final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass; final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet(); //反射獲取SelectedKeys、publicSelectedKeys,通過Netty生成相應替代類 Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { try { //反射獲取屬性 Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys"); Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys"); if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) { // Let us try to use sun.misc.Unsafe to replace the SelectionKeySet. // This allows us to also do this in Java9+ without any extra flags. long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField); long publicSelectedKeysFieldOffset = PlatformDependent.objectFieldOffset(publicSelectedKeysField); if (selectedKeysFieldOffset != -1 && publicSelectedKeysFieldOffset != -1) { PlatformDependent.putObject( unwrappedSelector, selectedKeysFieldOffset, selectedKeySet); PlatformDependent.putObject( unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet); return null; } // We could not retrieve the offset, lets try reflection as last-resort. } Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true); if (cause != null) { return cause; } cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true); if (cause != null) { return cause; } selectedKeysField.set(unwrappedSelector, selectedKeySet); publicSelectedKeysField.set(unwrappedSelector, selectedKeySet); return null; } catch (NoSuchFieldException e) { return e; } catch (IllegalAccessException e) { return e; } } }); if (maybeException instanceof Exception) { selectedKeys = null; Exception e = (Exception) maybeException; logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e); return new SelectorTuple(unwrappedSelector); } selectedKeys = selectedKeySet; logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector); return new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet)); } //運行EventLoop protected void run() { cancelledKeys = 0; needsToSelectAgain = false; final int ioRatio = this.ioRatio; boolean ranTasks; if (ioRatio == 100) {//百分百處理IO任務 try { processSelectedKeys();//執行IO操作 } finally { // Ensure we always run tasks. ranTasks = runAllTasks();//執行非IO任務 } } } //處理IO任務 private void processSelectedKeysOptimized() { for (int i = 0; i < selectedKeys.size; ++i) {//循環所有Key final SelectionKey k = selectedKeys.keys[i]; // null out entry in the array to allow to have it GC'ed once the Channel close // See https://github.com/netty/netty/issues/2363 selectedKeys.keys[i] = null; final Object a = k.attachment(); if (a instanceof AbstractNioChannel) { processSelectedKey(k, (AbstractNioChannel) a);//通過Select中OPS對事件進行分發 } else { @SuppressWarnings("unchecked") NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a; processSelectedKey(k, task); } if (needsToSelectAgain) { // null out entries in the array to allow to have it GC'ed once the Channel close // See https://github.com/netty/netty/issues/2363 selectedKeys.reset(i + 1); selectAgain(); i = -1; } } } // returns true if selectCnt should be reset //這里是Netty對epool bug的規避 private boolean unexpectedSelectorWakeup(int selectCnt) {//這里判斷是否要重建Selector //通過io.netty.selectorAutoRebuildThreshold進行配置 默認512 //如果selectCnt(輪詢次數)次數大于配置值,就進行重建Selector if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) { // The selector returned prematurely many times in a row. // Rebuild the selector to work around the problem. logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.", selectCnt, selector); rebuildSelector();//重建Selector 避免Selector空輪詢 return true; } return false; }
- 工作原理
五、總結
Netty作為高性能的NIO通信框架,延續了NIO的諸多特性的同時,還對NIO存在的問題,做統一的規避與優化,主要從內存、線程、編解碼、傳輸等方面,通過巧妙地線程模型設計、堆與直接內存的合理使用、環形數組緩沖區、無鎖并發、內存池化重用、引用計數器等技術對性能做了很大的提升,通過學習Netty架構在分層、穩定、擴展等方面的巧妙設計,受益良多。