Netty架構模型設計與源碼淺析

cover.jpg

本文主要從IO模型、Netty邏輯架構、Netty各組件的設計與應用為主導,由簡-難-細展開來介紹,其中包括IO模型以及BIO、NIO、AIO;
邏輯架構主要是如何分層、各層如何協作以及如何達到高性能、高可靠性等;組件主要介紹Buffer、Channel、ChannelPipeling、ChannelHandler、EvnetLoop等,最后從源碼的角度分析客戶端、服務端如何實現以及二者通訊中編解碼、讀寫半包等

  • 參考資料
    [1]李林鋒《Netty權威指南》 第2版
    [2] Netty官網

一、IO模型

  1. 阻塞IO模型


    阻塞IO模型.png

應用進程空間觸發內核調用,數據包準備直到復制到用戶空間或異常發生,此期間應用進程處于等待阻塞狀態,被稱為阻塞模型

  1. 非阻塞IO模型


    非阻塞.png

從應用層到內核,若該緩存區沒有數據,立即返回EWouldBlock異常,通常進行輪詢,反復Recv,直到數據準備完成,與阻塞的區別:無數據的情況下,是否立即返回

  1. IO多路復用模型


    IO復用模型.png

應用進程通過Select將一個或多個FD(File Description)阻塞,select/poll是順序掃描FD數據是否就緒,Select能持有的FD有限;epoll中FD受限于操作系統的最大文件句柄數,其通過事件驅動替代順序掃描,只關心活躍的FD,當FD就緒,回調rallBack,性能更好,同時epoll通過內核與用戶空間mmap使用同一塊內存來實現的,此期間Select與數據復制都會阻塞應用線程

  1. 信號驅動IO模型


    信號驅動IO模型.png

該模型與復用模型類似,通過信號驅動與通知完成數據準備就緒的判別,同時此過程非阻塞,這是和復用模型的區別

  1. 異步IO模型


    異步IO模型.png

該模型與信號驅動類似,觸發內核某個操作,數據準備就緒,復制完成后,通知應用程序,與信號驅動的區別在于是否已經完成數據復制后通知

  1. BIO

    • 同步


      BIO同步通信模型.png

    由獨立的Acceptor線程負責監聽客戶端連接,然后為每個連接創建新的線程,處理完成后返回給客戶端,最大的問題就是缺乏彈性伸縮能力,當客戶并突增后,服務器線程膨脹,性能會下降,線程堆棧溢出、新建線程失敗等問題,最后導致系統宕機。

    • 偽異步


      BIO通信模型(偽異步).png

    通過將客戶端Socket封裝成Task,投遞到線程池,通過線程對Socket做統一控制,解決了線程突增問題,但是由于底層通信還是同步阻塞模型,沒有從根本解決問題,當所有可用線程被阻塞,后續Socket進入隊列,隊列滿后,新請求甚至被直接拒絕掉,大量響應超時,導致客戶端認為服務器癱瘓

  2. NIO

    NIO通訊模型.png

    NIO有三大核心類,Buffer、Channel、Selector,Buffer與Channel一一對應,Channel與Selector是多對一。Channel注冊到Selector,Selector輪詢就緒的Key,根據事件在Channel間切換,異步讀取消息到Buffer,通過解碼處理,最后可以發送消息Buffer到SocketChannel,通過Selector事件,通知事件注冊Key處理消息,Socket連接以及讀寫都是異步的,同時Selector通過epool實現,所以非常適合做高性能、高負載的網絡服務器

  3. AIO
    使用Proactor模式,通過Future、CompletionHandler實現異步操作結果以及通知的實現,不需要通過多路復用即可實現異步讀寫,從而簡化編程模型,由于不是本文主要內容,所以不仔細分析

  4. 零拷貝
    從操作系統層面講,數據不經過CPU拷貝從一個內從區域到另一個內存區域,內核緩沖區沒有產生重復數據

    • 傳統IO


      傳統IO拷貝.png

      經過四次內存拷貝,四次上下文形態的切換

      1. JVM發起read,OS切換到內核態(第一次狀態切換),數據從Hardware通過DMA拷貝到Kernel(第一次拷貝)
      2. OS切換到用戶態(第二次狀態切換),將數據從kernel通過CPU拷貝到User(第二次拷貝)
      3. JVM發write,OS切換形態到內核態(第三次狀態切換),將數據拷貝到Kernel(第三次拷貝),然后切換到用戶態(第四次狀態切換)數據拷貝到Hardware(第四次拷貝)
    • Mmap優化


      MMAP-IO拷貝.png

      三次內存拷貝,四次上下文形態的切換
      整個數據流與傳統IO相似,區別在于節省了第二次拷貝,內核緩沖數據可以與用戶共享。

    • SendFile


      SendFile-IO拷貝 -.png

      帶有DMA收集拷貝功能的SendFile是倆次拷貝,倆次內核的切換

      1. OS切換內核態,Hardware拷貝數據到Kernel
      2. Kernel拷貝Length、Offset到 Kernel(不計做數據拷貝)
      3. 傳統I/O用戶空間緩存了數據,所以應用程序可以對數據進行修改
      4. 通過Length、Offset直接將Kernel數據拷貝到協議引擎
      5. Java NIO中transferTo實現了該功能

二、Netty概述

Netty是在NIO問題眾多的情況下應聲而來,基于NIO提供了異步非阻塞、事件驅動的網絡應用程序框架與工具

  • NIO存在的問題
    1. 類庫、API復雜,依賴額技能:多線程
    2. 可靠性能力的補齊,工作量和難度都很大:斷連重連、網絡閃斷、讀寫半包、失敗緩存、網絡擁堵、異常碼流的處理
  • Netty應聲而來
    1. API簡單、開發門檻低
    2. 功能強大,預置多種編解碼,支持多種主流協議
    3. 定制能力強,通過ChannelHandler,對通信框架靈活擴展
    3. 性能高、成熟、穩定,社區活躍

三、Netty架構設計

  1. 架構模型


    Netty邏輯機構圖.png

    整體采用三層網絡架構進行設計開發,實現了NIO架構各層之間的解耦,便于上層協議棧的開發與邏輯的定制;Reactor層作為通信調度,PipeLine層攔截監聽事件,Service ChannelHandler 用于擴展與業務邏輯的編排,下面詳細介紹各層職責

    • Reactor通信調度
      主要負責網絡的連接以及讀寫操作,將網絡層的數據讀取到內存緩存區中,然后觸發各種網絡事件,例如:連接創建、連接激活、讀寫事件等,同時將事件回調到Pipeline中,由PipeLine做后續處理
    • PipeLine職責鏈
      復制事件在職責鏈中的有序傳播,同時負責動動態的編排,選擇監聽和處理自己感興趣的事件,可以攔截處理與向前\后傳播事件,通常不同Handler節點功能不同,例如編解碼Handler,負責消息協議轉化,這樣上層只需要關心業務邏輯,不需要感知底層的協議、線程模型的差異,實現了架構的分層隔離
    • Service Handler 業務處理
      可以分為倆類,一類是純粹的業務邏輯處理,另一類就是其他應用層的協議插件,用于特定協議相關的會話和鏈路管理
  2. 服務端 & 客戶端 創建過程時序

    • 服務端


      服務端順序圖.png
    • 客戶端


      客戶端順序圖圖.png
  3. 線程控制
    由于Netty采用異步通信模型,一個IO線程并發處理多個客戶端連接讀寫,IO多路復用 + 池化技術其實就是Reactor的基本設計思想,Reactor單獨運行在一個線程,負責監聽與分發事件,分發給適當的程序來處理IO事件,通過Handler實際處理相關事件,那么線程模型到底是什么樣,下面詳細講解

    • 單Reactor單線程模型


      Reactor單線程.png
      1. 典型的IO多路復用通過一個線程阻塞多路連接請求,Reactor通過select監聽客戶端事件,收到后通過Dispatch進行分發事件;
      2. 如果是連接事件,交由Acceptor進行連接處理,創建Handler完成后續處理;
      3. 如果非連接請求,分發到相應的handler處理,完成read -> 業務處理 - > send 過程處理

      優點:模型簡單,無多線程,不存在進程通訊、資源競爭問題
      缺點:性能問題:單線程無法發揮多核優勢,同時處理Handler業務會阻塞線程,導致客戶端連接超時,往往超時會重試,加重服務端壓力,惡性循環;可靠性問題:線程意外停止或進入死循環會導致整個系統不可用
      使用于客戶端數據量有限,業務響應迅速的應用

    • 單Reactor多線程模型


      Reactor多線程 .png
      1. Reactor啟動單線程監聽客戶端事件,通過Dispatch分發事件
      2. 連接事件交由Acceptor進行連接處理,創建Handler完成后續處理
      3. 非連接事件,由Reactor分發到相應的Handler處理
      4. handler只負責響應事件,read讀取數據后,分發到worker線程池中做業務處理

      優點:發揮多核的處理能力
      缺點:單Reactor線程處理所有監聽事件,在高并發情況下會成為瓶頸,同時多線程共享和訪問數據比較復雜

    • Reactor主從多線程模型


      Reactor主從多線程.png
      1. Reactor主線程通過select監聽連接事件,收到連接通知Acceptor處理連接事件
      2. 連接建立后主線程分配給Reactor子線程,創建Handler進行各種事件處理
      3. 有事件發生,SubReactor調用相應的Handler,handler通過read后將數據分發給worker線程去處理
      4. 一個MainReactor可以對應多個SubReactor

    優點:主從線程數據交互簡單,職責明確,主線程負責接收連接,從線程負責后續業務處理,同時無需返回
    缺點:編程難度較大

    • Netty線程模型


      Netty線程模型.png

      該模式可以通過參數配置,支持以上三種Reactor線程模型

    1. 抽象出倆個線程池,Boss Group 負責客戶端連接請求,Worker Group負責網絡的讀寫,二者皆為NioEventLoopGroup,表示事件循環組,每一循環為NioEventLoop
    2. NioEventLoop表示一個不斷循環處理任務的線程,內置Selector監聽綁定在其上面的Socket通訊事件
    3. 每個Boss NioEventLoop包含三個步驟
      • 輪詢監聽accept事件
      • 處理accept事件,與client建立連接,生成NioSocketChannel,并將其注冊到Worker NioEventLoop中的Selector
      • 處理任務隊列任務
    4. 每個Worker NioEventLoop循環執行步驟
      • 輪詢監聽read、write事件
      • 在對應的NioSocketChannel處理read、write事件
      • 處理任務隊列任務
    5. 每個Worker NioEventLoop處理業務會通過PipeLine,其中內置了很多處理器,支持業務處理
  1. Handler調用機制


    Pipeline事件攔截和處理流程.png

在Netty架構設計中,Handler充當了處理入棧和出棧的數據處理邏輯的容器,實現相應接口(ChannelInBoundHandler)重寫相應方法就可以接收入棧事件和數據,將數據進行邏輯處理;向客戶端發送響應時,可用從InBound中沖刷數據,ChannelOutBoundHandler原理相同,處理出棧數據;其中入棧是相對PipeLine來說的,PipeLine中以LinkedList線程存儲Handler,從Socket中read數據到Pipeline進行處理稱為入棧,Handler執行順序為HeadHandler -> TailHandler,反之write到Socket稱為出棧,執行順序TailHandler - > HeadHandler

  1. 編解碼與協議開發
    當Netty接收和發送消息時,就會發生一次數據轉換,根據入棧和出棧分別進行解碼和編碼,Netty自身提供了一系列編解碼器,都會實現ChannelInBoundHandler、ChannelOutBoundHandler接口并在channelRead中接收數據,進行數據的編解碼,數據處理后會轉發到寫一個Handler;由于TCP是 “流” 協議,也就是傳輸的數據是沒有界限的,所以導致在業務層面講不能確定每次發送是一條完整數據,這就是TPC粘包、拆包問題
    • TCP粘包、拆包問題
      底層TCP無法理解上層業務數據,所以導致底層無法保證數據包不被拆分和重組,所以這個問題只能通過上層協議棧設計去解決,根據業界與主流協議的解決方案,可以總結為以下幾種
      1. 消息定長
      2. 在包尾添加特殊字符進行分割,比如:回車
      3. 將消息分為消息頭、消息體,消息頭保存消息長度
      4. 更復雜的應用層協議
    • 消息序列化
      當進行遠程跨進程調用時,需要將被傳輸的Java對象編碼為字節數組或ByteBuffer對象,目標系統需要將接收到的數據進行相應解碼為Java對象,其中最常見的就是Java的序列化,實現Serializable接口
      1. Java序列化
        序列化目的:網絡傳輸(本文重點)、對象持久化;
        序列化是Java編碼中的一種,由于其種種缺陷,衍生出多種編
        解碼技術與框架
        缺陷:無法跨語言、序列化后的體積較大、性能低

      2. Protobuf
        是一種與語言無關、平臺無關、性能高、擴展性好的數據序列化方法,可以用于數據傳輸協議以及數據存儲,可類比于XML但是比XML更小、更快、更簡單,通過數據描述文件和代碼生成機制實現以上特點

      3. Thrift

        Thrift是一種接口描述語言和二進制通訊協議,它被用來定義和創建跨語言的服務。它被當作一個遠程過程調用(RPC)框架來使用,是由Facebook為“大規模跨語言服務開發”而開發的。——來源:百度百科

      4. Mershalling
        是一個Java對象序列話的API包,修正了JDK自帶序列化的諸多問題,在兼容Serializable的同時,新增可調參數與附加特性

        • 可插拔的類解析器,提供更加便捷的定制策略,
        • 可插拔的對象替換技術
        • 無需實現指定接口
        • 通過緩沖提高性能
  2. 如何達到架構質量指標
    • 高性能

      性能是設計出來的,而不是測試出來的

      影響網絡通信性能多,主要從傳輸、協議、進程三個方面看,選取怎么樣內存模型、采用怎么樣的通信協議、線程模型如何選擇,下面詳細介紹Netty在這些方面是如何做的

      1. 采用異步非阻塞的IO類庫,基于Reactor模式實現,平滑的處理客戶端線性增長;通過NioEventLoop聚合異步多路復用Selector,并發處理成百上千個SocketChannel的讀寫,極大提升性能、彈性伸縮能力;
      2. 網絡傳輸緩沖區使用 直接內存,避免內存復制,提高IO讀寫性能;
        通過三種方面體現零拷貝
        • Netty的讀寫采用DirectBuffer堆外內存直接內存,相比于堆內存要講堆內存緩存區數據拷貝到直接內存, 不需要進行字節緩沖區的二次拷貝
        • 通過實現CompositeByteBuf,將多個ByteBuf分裝成一個ByteBuf,對外提供統一ByteBuf接口,實際就是一個裝飾器,將多個ByteBuf組合成一個集合,這樣避免了內存拷貝;
        • 文件傳輸中通過transferTo方法直接文件發送到目標Channel中, 不需要進行循環拷貝(底層SendFile)
      3. 支持通過內存池的方式循環利用ByteBuffer,避免頻繁創建、銷毀Buffer對象造成的性能開銷;雖然JVM虛擬機與JIT及時編輯編譯的發展,對象的分配和回收是個非常輕量級的工作,但是對于緩沖區的情況稍有不同,緩沖區操作頻繁,同時還是堆外內存,這樣分配和回收對象就會稍有耗時。為了盡量重用緩沖區,Netty提供了基于內存池的緩沖區重用機制,通過啟動輔助類中配置相關參數,事項差異化定制;PooledByteBufAllocator.DEFAULT.directBuffer(1024)
      4. 可配置的IO線程數、TCP參數等,為不同的應用場景提供定制化的調優參數;通過輔助類的參數配置,可以使Nettry支持任意一種Reactor線程模型,支持不同的業務場景,同時合理地設置TCP參數滿足不同的用戶場景
      5. 采用環形數組緩沖區實現無鎖化并發編程,替代傳統的線程安全容器以及鎖;在數據讀寫時,采用循環數數組緩沖區如ChannelOutBoundBuffer,每次寫入通道,然后彈出,直到無內容
      6. 合理地使用線程安全容器、原子類等,提高系統的并發處理能力;volatile的大量、正確使用;CAS和原子類的廣泛使用;線程安全容器的使用;讀寫鎖提高并發性能
      7. 關鍵資源使用單線程串行化,避免多線程并發訪問帶來的鎖競爭和額外的CPU資源消耗問題;表面上串行化設計似乎CPU利用率不高,并發程度不夠,但是通過Worker NioEventLoop的線程池的參數,可以同時啟動多個串行化的線程并行運行,這種局部無鎖化的穿串行線程設計相比一個隊列,多個工作線程性能更優
      8. 通過引用計數器及時的釋放不在應用的對象,細粒度的內存管理降低GC的頻率,AbstractReferenceCountedByteBuf
    • 可靠性
      作為高性能的異步通訊框架,架構的可靠性是重要硬性指標

      1. 鏈路有效性檢測
        基于長連接帶來諸多便利,但鏈路的有效性沒有保證,Netty可以通過心跳周期性的檢測,在系統空閑無業務時可以識別網絡閃斷、網絡單通等網絡異常進行自動關閉、重連,使系統空閑與高峰能順利過度;同時業務消息可以充當鏈路檢測,心跳只需要系統空閑時發送;
        心跳機制:Ping - Pong 、Ping - Ping
        Netty提供相關類庫:io.netty.handler.timout.*,進行空閑檢測,主要監聽讀空閑、寫空閑、讀寫空閑事件,自定義邏輯代碼實現協議層的心跳檢測
      2. 內存保護機制
        通過對象引用計數器對Bytebuf等內置對象進行細粒度的內存申請與釋放,對非法的對象引用做檢測與保護,通過內存池對ByteBuf重用、分配機制(預分配、協議定長)、設置內充容量上限(鏈路總數、單個緩沖區大小、消息長度)
      3. 優雅停機
        基于JVM發送退出信號量,Netty對所有涉及資源釋放、回收的地方,都采用了優雅退出的方式,也就是退出前釋放相關模塊資源占用、將緩沖區的消息處理完成、待刷新數據持久化到硬盤或數據庫,所有處理執行完畢后退出
    • 可定制
      主要體現在Pipelin的職責鏈設計,便于業務邏輯的定制化;基于接口開發;提供大量工廠類、配置化輔助類,可以按照業務需求進行配置、創建對象;

    • 可擴展
      很方便的進行應用層協議定制擴展

四、核心組件工作原理及源碼解析

以下所有源碼分析基于Netty 4.1版本

  1. Buffer(以ByteBuffer為主)

    • 工作原理
      1. 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位置追加
          
      2. JDK原生java.nio.ByteBuffer中長度固定,由于只有一個位置標識指針,每次讀寫需要flip()等操作進行指正切換,API有限,高級功能需要自己去實現;基于以上問題,Netty提供了自己的Buffer實現,主要采用倆中策略 ①參考JDK實現,處理缺陷、擴展功能;②采用Facade模式,聚合并包裝JDK原生Buffer
      3. ByteBuf通過readerIndex & writerIndex倆個指針完成緩沖區的讀寫避免flip,通過擴容解決定容問題


        ByteBuf.png
      4. 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() --可讀緩存區
          
      5. 內存池原理
        為了集中管理內存,預先分配一大塊連續內存的分配與釋放,同時由于頻繁調用系統來申請內存從而提高性能大大提升,Netty中PoolArena是由多個Chunk組成的大塊內存區域,每個Chunk是由多個Page組成,PoolChunk主要用來組織管理多個Page的內存分配和釋放,其中的Page被構建成二叉樹,內存選擇是對樹的深度優先遍歷,但是同層隨機選擇,如果分配內存小于page,PoolSubpage會分成相等的塊,分多大是由第一次申請決定的,后面所有的申請都按這個來,無論是Chunk還是Page都是通過狀態位來標識是否可用
      6. 相關類
        • ByteBufHolder:對ByteBuf的一層封裝,便于協議攜帶數據在消息體中
        • ByteBufAllocator:Buf分配器,上面源碼涉及到,主要分池與非池的Buf的分配;
        • ByteBufUtil : ByteBuf工具類
    • 源碼解析
      1. 繼承關系


        Diagram-ByteBuf.png

        以上是ByteBuf主要功能類圖,從內存分配來看,分為堆內存、直接內存,二者的區別前面已經提過,無非就是分配&回收、復制方面各有利弊,所以讀寫緩沖使用直接內存,編解碼使用堆內存,使系能到達最優;從內存的回收來看,可以分為池化與普通Buf,池化可以重用內存,提升效率,降低GC消耗,但是在維護管理更加復雜;Netty提供以上策略,共開發者選擇。

      2. 讀取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);
            }
        }
        
      3. 寫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);
        }
        
      4. 重用緩沖區(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;
        }
        
      5. 對引用計數進行分析(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)
        }
        
      6. 非池化堆內存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)

      7. 池化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);
            }
        }
        
  2. Channel

    • 工作原理
      1. 主要負責客戶端的連接、網絡的讀寫,鏈路的關閉、一些框架功能等,在原生Channel諸多不便的情況下,Netty重新設計
      2. 采用Facade封裝、功能齊全、聚合原生Channel統一分配調度,
      3. 核心方法
        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
        
    • 源碼解析
      1. 繼承關系


        Diagram-Channel.png
      2. 抽象統一處理的核心

        • 屬性
           //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);
              }
          }
          
      3. 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;
         }
        
      4. 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();//連接失敗,關閉連接
              }
          }
        }
        
      5. 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);
          }
        }
        //其他放方法不再贅述
        
  3. 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);//直接調用
       }
      }
      
  4. ChannelHandler

    • 工作原理
      1. 繼承關系


        Diagram-ChannelHandler.png
    • 源碼解析
      主要分析ByteToMessageDecoder
      public 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);
        }
      }
      
  5. EventLoop

    • 工作原理
      1. 繼承關系


        Diagram-EventLoop.png
      2. 是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架構在分層、穩定、擴展等方面的巧妙設計,受益良多。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379