前言
我們都知道Netty是一款用于創建高性能網絡應用程序的高級框架,但是實際工作中真正地去直接使用Netty的場景好像不多(反正我沒有)。其實Netty無處不在,很多中間件底層通信框架用的都是Netty,dubbo、rocketMQ、Elasticsearch等常用的框架和中間件其實都用到了Netty。最近在讀《Netty實戰》這本書,做一個知識點的簡要概括吧,省略了一些章節(比如Netty用于單元測試等)。
異步和事件驅動
bio線程模型:
nio線程模型:
Netty 的核心組件
- Channel
- 回調
- Future
-
事件和 ChannelHandler
image
Netty的組件和設計
Netty網絡抽象的代表
- Channel— Socket
- EmbeddedChannel
- LocalServerChannel
- NioDatagramChannel
- NioSctpChannel
- NioSocketChannel
-
EventLoop— 控制流、多線程處理、并發
image - ChannelFuture— 異步通知
ChannelHandler 和 ChannelPipeline
- 應用程序的業務邏輯通常駐留在一個或者多個 ChannelInboundHandler 中
-
ChannelPipeline 提供了 ChannelHandler 鏈的容器
image - 在Netty中,有兩種發送消息的方式。你可以直接寫到Channel中,也可以寫到和ChannelHandler 相關聯的 ChannelHandlerContext 對象中。前一種方式將會導致消息從 ChannelPipeline 的尾端開始流動,而后者將導致消息從 ChannelPipeline 中的下一個 ChannelHandler 開始流動。
- channelHandler的應用:編碼器和解碼器
-
服務端EventLoopGroup
image
傳輸
分類
- OIO——阻塞傳輸
- NIO——異步傳輸
- Local——JVM 內部的異步通信
- Embedded——測試你的ChannelHandler
channel
每個 Channel 都將會被分配一個 ChannelPipeline 和 ChannelConfig。 ChannelConfig 包含了該 Channel 的所有配置設置,并且支持熱更新。
Netty 的 Channel 實現是線程安全的。
-
NIO處理channel狀態
image 關注linux的epoll實現
-
應用程序最佳傳輸實現
image
數據容器ByteBuf
優點
- 可以被用戶自定義的緩沖區類型擴展
- 通過內置的復合緩沖區類型實現了透明的零拷貝
- 容量可以按需增長(類似于 JDK 的 StringBuilder)
- 在讀和寫這兩種模式之間切換不需要調用 ByteBuffer 的 flip()方法
- 讀和寫使用了不同的索引
- 支持方法的鏈式調用
- 支持引用計數
- 支持池化
ByteBuf使用模式
- 堆緩沖區,支撐數組
- 直接緩沖區
- 復合緩沖區
字節級操作
-
丟棄字節:discardReadBytes()
image -
索引復位:clear()
image - 切片:slice();副本:copy()
重要的類
- ByteBufHolder,持有ByteBuf引用,可以存儲額外信息
- ByteBufAllocator,獲取池化ByteBuf,可以從Channel或者ChannelHandlerContext獲取ByteBufAllocator的引用
- Unpooled,獲取未池化ByteBuf
- 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);
}
}
資源管理
- 每當通過調用 ChannelInboundHandler.channelRead()或者 ChannelOutboundHandler.write()方法來處理數據時,都需要確保沒有任何的資源泄漏。除非調用了ChannelHandlerContext.fireChannelRead()交給下一個處理器處理。
- ChannelOutboundHandler.write()不僅要釋放資源,還要通知ChannelPromise。否則可能會出現 ChannelFutureListener 收不到某個消息已經被處理了的通知的情況。
-
資源泄露檢測類——ResourceLeakDetector
利用了JDK的虛引用,配置參數java -Dio.netty.leakDetectionLevel=ADVANCED
image
ChannelPipeline接口
- 每一個新創建的 Channel 都將會被分配一個新的 ChannelPipeline。這項關聯是永久性的; Channel 既不能附加另外一個 ChannelPipeline,也不能分離其當前的。在 Netty 組件 的生命周期中,這是一項固定的操作,不需要開發人員的任何干預。
- ChannelInboundHandler從頭部開始處理,ChannelOutboundHandler從尾部開始處理
ChannelHandlerContext接口
- ChannelHandlerContext 有很多的方法,其中一些方法也存在于 Channel 和 Channel- Pipeline 本身上,但是有一點重要的不同。如果調用 Channel 或者 ChannelPipeline 上的這 些方法,它們將沿著整個 ChannelPipeline 進行傳播。而調用位于 ChannelHandlerContext 上的相同方法,則將從當前所關聯的 ChannelHandler 開始,并且只會傳播給位于該 ChannelPipeline 中的下一個能夠處理該事件的 ChannelHandler。
-
重要組件之間的關系
image -
事件傳播
image
image
異常處理
- 通過調用 ChannelPromise 上的 setSuccess()和 setFailure()方法,可以使一個操作的狀態在 ChannelHandler 的方法返回給其調用者時便即刻被感知到
EventLoop和線程模型
類層次結構
EventLoop實現細節
-
執行邏輯
image -
分配方式
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()
- 它將處理任何掛起的事件和任務,并且隨后釋放所有活動的線程
編解碼器
編碼器操作出站數據,而解碼器處理入站數據。
編解碼器中的引用計數是會自動處理的,ReferenceCountUtil.release(message)
解碼器
- ByteToMessageDecoder
- ReplayingDecoder
- MessageToMessageDecoder
- TooLongFrameException 防止解碼器緩沖的數據耗盡內存
編碼器
- MessageToByteEncoder
- MessageToMessageEncoder
編解碼器
- ByteToMessageCodec
- MessageToMessageCodec
- CombinedChannelDuplexHandler
預置的 ChannelHandler 和編解碼器
SslHandler
在大多數情況下,SslHandler將是ChannelPipeline中的第一個ChannelHandler。 這確保了只有在所有其他的 ChannelHandler 將它們的邏輯應用到數據之后,才會進行加密。
HTTP 解碼器、編碼器和編解碼器
- HttpRequestEncoder
- HttpResponseEncoder
- HttpRequestDecoder
- HttpResponseDecoder
- HttpObjectAggregator——聚合 HTTP 消息
-
HttpContentCompressor && HttpContentDecompressor——HTTP 壓縮和解壓
image
WebSocketProtocolHandler
空閑的連接和超時
- IdleStateHandler——可以用來實現心跳
- ReadTimeoutHandler
- WriteTimeoutHandler
基于分隔符的協議
- DelimiterBasedFrameDecoder
- LineBasedFrameDecoder——比DelimiterBasedFrameDecoder效率高
基于長度的協議
LineBasedFrameDecoder
- FixedLengthFrameDecoder
- LengthFieldBasedFrameDecoder
寫大型數據
- FileRegion
- 通過支持零拷貝的文件傳輸的 Channel 來發送的文件區域
- 只適用于文件內容的直接傳輸,不包括應用程序對數據的任何處理
- ChunkedWriteHandler
- 支持將數據從文件系統復制到用戶內存中
- 支持異步寫大型數據流,而又不會導致大量的內存消耗
序列化數據
- JBoss Marshalling
- Protocol Buffers
擴展——研究Netty中碰到的思考
- netty解決的epoll空輪詢
- FastThreadLocal實現原理
- EventLoopGroup分配EventLoop的實現細節
類似于輪詢,用原子變量來實現索引遞增,做了一個優化,如果總數是2的倍數,會通過位移來計算余數
- ChannelProgressivePromise實時獲取傳輸進度怎么實現