《Netty實戰》總結

前言

我們都知道Netty是一款用于創建高性能網絡應用程序的高級框架,但是實際工作中真正地去直接使用Netty的場景好像不多(反正我沒有)。其實Netty無處不在,很多中間件底層通信框架用的都是Netty,dubbo、rocketMQ、Elasticsearch等常用的框架和中間件其實都用到了Netty。最近在讀《Netty實戰》這本書,做一個知識點的簡要概括吧,省略了一些章節(比如Netty用于單元測試等)。

異步和事件驅動

bio線程模型:

image

nio線程模型:

image

Netty 的核心組件

  • Channel
  • 回調
  • Future
  • 事件和 ChannelHandler


    image

Netty的組件和設計

Netty網絡抽象的代表

  • Channel— Socket
  1. EmbeddedChannel
  2. LocalServerChannel
  3. NioDatagramChannel
  4. NioSctpChannel
  5. NioSocketChannel
  • EventLoop— 控制流、多線程處理、并發


    image
  • ChannelFuture— 異步通知

ChannelHandler 和 ChannelPipeline

  • 應用程序的業務邏輯通常駐留在一個或者多個 ChannelInboundHandler 中
  • ChannelPipeline 提供了 ChannelHandler 鏈的容器


    image
  • 在Netty中,有兩種發送消息的方式。你可以直接寫到Channel中,也可以寫到和ChannelHandler 相關聯的 ChannelHandlerContext 對象中。前一種方式將會導致消息從 ChannelPipeline 的尾端開始流動,而后者將導致消息從 ChannelPipeline 中的下一個 ChannelHandler 開始流動。
  • channelHandler的應用:編碼器和解碼器
  • 服務端EventLoopGroup


    image

傳輸

分類

  1. OIO——阻塞傳輸
  2. NIO——異步傳輸
  3. Local——JVM 內部的異步通信
  4. Embedded——測試你的ChannelHandler

channel

image
  • 每個 Channel 都將會被分配一個 ChannelPipeline 和 ChannelConfig。 ChannelConfig 包含了該 Channel 的所有配置設置,并且支持熱更新。

  • Netty 的 Channel 實現是線程安全的。

  • NIO處理channel狀態


    image
  • 關注linux的epoll實現

  • 應用程序最佳傳輸實現


    image

數據容器ByteBuf

優點

  1. 可以被用戶自定義的緩沖區類型擴展
  2. 通過內置的復合緩沖區類型實現了透明的零拷貝
  3. 容量可以按需增長(類似于 JDK 的 StringBuilder)
  4. 在讀和寫這兩種模式之間切換不需要調用 ByteBuffer 的 flip()方法
  5. 讀和寫使用了不同的索引
  6. 支持方法的鏈式調用
  7. 支持引用計數
  8. 支持池化

ByteBuf使用模式

  1. 堆緩沖區,支撐數組
  2. 直接緩沖區
  3. 復合緩沖區

字節級操作

image
  1. 丟棄字節:discardReadBytes()


    image
  2. 索引復位:clear()


    image
  3. 切片:slice();副本:copy()

重要的類

  1. ByteBufHolder,持有ByteBuf引用,可以存儲額外信息
  2. ByteBufAllocator,獲取池化ByteBuf,可以從Channel或者ChannelHandlerContext獲取ByteBufAllocator的引用
  3. Unpooled,獲取未池化ByteBuf
  4. ByteBufUtil,工具類

引用計數

ReferenceCounted接口

ChannelHandler和ChannelPipeline

  • 當某個 ChannelInboundHandler 的實現重寫 channelRead()方法時,它將負責顯式地 釋放與池化的 ByteBuf 實例相關的內存。Netty 為此提供了一個實用方法 ReferenceCount- Util.release()
public class DiscardHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ReferenceCountUtil.release(msg); 
    }
}

資源管理

  1. 每當通過調用 ChannelInboundHandler.channelRead()或者 ChannelOutboundHandler.write()方法來處理數據時,都需要確保沒有任何的資源泄漏。除非調用了ChannelHandlerContext.fireChannelRead()交給下一個處理器處理。
  2. ChannelOutboundHandler.write()不僅要釋放資源,還要通知ChannelPromise。否則可能會出現 ChannelFutureListener 收不到某個消息已經被處理了的通知的情況。
  3. 資源泄露檢測類——ResourceLeakDetector
    利用了JDK的虛引用,配置參數java -Dio.netty.leakDetectionLevel=ADVANCED


    image

ChannelPipeline接口

  1. 每一個新創建的 Channel 都將會被分配一個新的 ChannelPipeline。這項關聯是永久性的; Channel 既不能附加另外一個 ChannelPipeline,也不能分離其當前的。在 Netty 組件 的生命周期中,這是一項固定的操作,不需要開發人員的任何干預。
  2. ChannelInboundHandler從頭部開始處理,ChannelOutboundHandler從尾部開始處理

ChannelHandlerContext接口

  1. ChannelHandlerContext 有很多的方法,其中一些方法也存在于 Channel 和 Channel- Pipeline 本身上,但是有一點重要的不同。如果調用 Channel 或者 ChannelPipeline 上的這 些方法,它們將沿著整個 ChannelPipeline 進行傳播。而調用位于 ChannelHandlerContext 上的相同方法,則將從當前所關聯的 ChannelHandler 開始,并且只會傳播給位于該 ChannelPipeline 中的下一個能夠處理該事件的 ChannelHandler。
  2. 重要組件之間的關系


    image
  3. 事件傳播


    image

    image

異常處理

  • 通過調用 ChannelPromise 上的 setSuccess()和 setFailure()方法,可以使一個操作的狀態在 ChannelHandler 的方法返回給其調用者時便即刻被感知到

EventLoop和線程模型

類層次結構

image

EventLoop實現細節

  1. 執行邏輯


    image
  2. 分配方式


    image

    image

引導

Bootstrap——客戶端或無連接協議

  • Bootstrap 類負責為客戶端和使用無連接協議的應用程序創建 Channel


    image

ServerBootstrap——服務端

  • ServerChannel 的實現負責創建子 Channel,這些子 Channel 代表了已被接受的連接


    image

DatagramChannel——無連接

Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new OioEventLoopGroup()).channel(
OioDatagramChannel.class).handler(new SimpleChannelInboundHandler<DatagramPacket>(){
    @Override
    public void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
        // Do something with the packet
    }
);}
ChannelFuture future = bootstrap.bind(new InetSocketAddress(0));
future.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture channelFuture) throws Exception {
        if (channelFuture.isSuccess()) {
            System.out.println("Channel bound");
        } else {
            System.err.println("Bind attempt failed");
            channelFuture.cause().printStackTrace(); 
        }
}); 

關閉

  • EventLoopGroup.shutdownGracefully()
  1. 它將處理任何掛起的事件和任務,并且隨后釋放所有活動的線程

編解碼器

編碼器操作出站數據,而解碼器處理入站數據。
編解碼器中的引用計數是會自動處理的,ReferenceCountUtil.release(message)

解碼器

  1. ByteToMessageDecoder
  2. ReplayingDecoder
  3. MessageToMessageDecoder
  4. TooLongFrameException 防止解碼器緩沖的數據耗盡內存

編碼器

  1. MessageToByteEncoder
  2. MessageToMessageEncoder

編解碼器

  1. ByteToMessageCodec
  2. MessageToMessageCodec
  3. CombinedChannelDuplexHandler

預置的 ChannelHandler 和編解碼器

SslHandler

在大多數情況下,SslHandler將是ChannelPipeline中的第一個ChannelHandler。 這確保了只有在所有其他的 ChannelHandler 將它們的邏輯應用到數據之后,才會進行加密。


image

HTTP 解碼器、編碼器和編解碼器

  1. HttpRequestEncoder
  2. HttpResponseEncoder
  3. HttpRequestDecoder
  4. HttpResponseDecoder
  5. HttpObjectAggregator——聚合 HTTP 消息
  6. HttpContentCompressor && HttpContentDecompressor——HTTP 壓縮和解壓


    image

WebSocketProtocolHandler

image

空閑的連接和超時

  1. IdleStateHandler——可以用來實現心跳
  2. ReadTimeoutHandler
  3. WriteTimeoutHandler

基于分隔符的協議

  1. DelimiterBasedFrameDecoder
  2. LineBasedFrameDecoder——比DelimiterBasedFrameDecoder效率高

基于長度的協議

LineBasedFrameDecoder

  1. FixedLengthFrameDecoder
  2. LengthFieldBasedFrameDecoder

寫大型數據

  1. FileRegion
  • 通過支持零拷貝的文件傳輸的 Channel 來發送的文件區域
  • 只適用于文件內容的直接傳輸,不包括應用程序對數據的任何處理
  1. ChunkedWriteHandler
  • 支持將數據從文件系統復制到用戶內存中
  • 支持異步寫大型數據流,而又不會導致大量的內存消耗

序列化數據

  1. JBoss Marshalling
  2. Protocol Buffers

擴展——研究Netty中碰到的思考

  1. netty解決的epoll空輪詢
  2. FastThreadLocal實現原理
  3. EventLoopGroup分配EventLoop的實現細節

類似于輪詢,用原子變量來實現索引遞增,做了一個優化,如果總數是2的倍數,會通過位移來計算余數

  1. ChannelProgressivePromise實時獲取傳輸進度怎么實現
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容