1. 程式人生 > >你真的瞭解Netty中@Sharable? | 併發程式設計網

你真的瞭解Netty中@Sharable? | 併發程式設計網

一、前言

Netty 是一個可以快速開發網路應用程式的基於事件驅動的非同步 網路通訊 框架,它大大簡化了 TCP 或者 UDP 伺服器的網路程式設計。Netty 的應用還是比較廣泛的,比如阿里巴巴開源的 Dubbo 和 Sofa-Bolt等 框架底層網路通訊都是基於 Netty 來實現的。Netty的設計是精妙的,其中每個設計點都值得我們去深思,本節我們來看看Netty中@Sharable的設計哲學

二、Netty基礎快速回顧

如果你熟悉netty請直接看第三節

io.netty.channel.Channel 是 Netty 框架自己定義的一個通道介面,Netty 實現的客戶端 NIO 套接字通道是 NioSocketChannel,提供的伺服器端 NIO 套接字通道是 NioServerSocketChannel。

  • NioSocketChannel:客戶端套接字通道,內部管理了一個 Java NIO 中的 java.nio.channels.SocketChannel 例項,用來建立 SocketChannel 例項和設定該例項的屬性,並呼叫 Connect 方法向服務端發起 TCP 連結等。
  • NioServerSocketChannel:伺服器端監聽套接字通道,內部管理了一個 Java NIO 中的 java.nio.channels.ServerSocketChannel 例項,用來建立 ServerSocketChannel 例項和設定該例項屬性,並呼叫該例項的 bind 方法在指定埠監聽客戶端的連結。
  • Channel 與 socket 的關係:在Netty中 Channel 有兩種,對應客戶端套接字通道 NioSocketChannel,內部管理 java.nio.channels.SocketChannel 套接字,對應伺服器端監聽套接字通道 NioServerSocketChannel,其內部管理自己的 java.nio.channels.ServerSocketChannel 套接字。也就是 Channel 是對 socket 的裝飾或者門面,其封裝了對 socket 的原子操作。
  • EventLoopGroup:Netty 之所以能提供高效能網路通訊,其中一個原因是因為它使用 Reactor 執行緒模型。在netty中每個 EventLoopGroup 本身是一個執行緒池,其中包含了自定義個數的 NioEventLoop,每個 NioEventLoop 是一個執行緒,並且每個 NioEventLoop 裡面持有自己的 selector 選擇器。

在 Netty 中客戶端持有一個 EventLoopGroup 用來處理網路 IO 操作,在伺服器端持有兩個 EventLoopGroup,其中 boss 組是專門用來接收客戶端發來的 TCP 連結請求的,worker 組是專門用來具體處理完成三次握手的連結套接字的網路 IO 請求的。

  • Channel 與 EventLoop 的關係:Netty 中 NioEventLoop 是 EventLoop 的一個實現,每個 NioEventLoop 中會管理自己的一個 selector 選擇器和監控選擇器就緒事件的執行緒;每個 Channel 只會關聯一個 NioEventLoop;

當 Channel 是客戶端通道 NioSocketChannel 時候,會註冊 NioSocketChannel 管理的 SocketChannel 例項到自己關聯的 NioEventLoop 的 selector 選擇器上,然後 NioEventLoop 對應的執行緒會通過 select 命令監控感興趣的網路讀寫事件;

當 Channel 是服務端通道 NioServerSocketChannel 時候,NioServerSocketChannel 本身會被註冊到 boss EventLoopGroup 裡面的某一個 NioEventLoop 管理的 selector 選擇器上,而完成三次握手的連結套接字是被註冊到了 worker EventLoopGroup 裡面的某一個 NioEventLoop 管理的 selector 選擇器上;

需要注意是多個 Channel 可以註冊到同一個 NioEventLoop 管理的 selector 選擇器上,這時候 NioEventLoop 對應的單個執行緒就可以處理多個 Channel 的就緒事件;但是每個 Channel 只能註冊到一個固定的 NioEventLoop 管理的 selector 選擇器上。

  • ChannelPipeline:Netty 中的 ChannelPipeline 類似於 Tomcat 容器中的 Filter 鏈,屬於設計模式中的責任鏈模式,其中鏈上的每個節點就是一個 ChannelHandler。在 netty 中每個 Channel 有屬於自己的 ChannelPipeline,對從 Channel 中讀取或者要寫入 Channel 中的資料進行依次處理, 如下圖是 netty 原始碼裡面的一個圖:
enter image description here

需要注意一點是雖然每個 Channel(更底層說是每個 socket)有自己的 ChannelPipeline,但是每個 ChannelPipeline 裡面可以複用通一個 ChannelHandler(也就是標註了@Sharable註解的handler可以被複用)。

三、ChannelHandler

上節我們提到每個 Channel(更底層說是每個 socket)有自己的 ChannelPipeline,每個 ChannelPipeline裡面管理者一系列的ChannelHandler。

正常情況下每個 Channel自己的 ChannelPipeline管理的同一個ChannelHandler Class物件的例項都是直接new的一個新例項,也就是原型模式,而不是單例模式。

image.png

如上圖當我們啟動Netty服務端時候,會設定childHander,這個childHander會當伺服器接受到完成TCP三次握手鍊接的時候給當前完成握手的Channel通道建立一個ChannelPipeline,並且建立一個EchoServerHandler的例項加入到Channel通道的ChannelPipeline。也就是說伺服器接受的所有Channel對應的ChannelPipeline裡面管理者自己的EchoServerHandler例項,而不是同一個。

但是有時候我們卻想讓不同Channel對應的ChannelPipeline裡面管理同一個EchoServerHandler例項,比如為了全域性的一些統計資訊,既然上面說當伺服器接受到完成TCP三次握手鍊接的時候給當前完成握手的Channel通道建立一個ChannelPipeline,並且建立一個EchoServerHandler的例項加入到Channel通道的ChannelPipeline,那麼我們建立一個單例的EchoServerHandler傳遞給childHandler是不是就可以了?我們修改上面程式碼如下:

image.png

這樣當伺服器接受到完成TCP三次握手鍊接的時候給當前完成握手的Channel通道建立一個ChannelPipeline,並且新增同一個EchoServerHandler的例項到對應管道。

啟動上面程式碼,然後客戶端發起多個連結時候,會有下面結果:

image.png

這是因為我們的EchoServerHandler沒有被新增@Sharable註解,現在新增如下:

image.png

再次執行就OK了。

具體檢查的程式碼是DefaultChannelPipeline類的checkMultiplicity的checkMultiplicity方法:

image.png image.png

可知當新增到不同管線的是不同的例項時候,不同連線在檢查時候h.added總是返回的false,所以不會丟擲異常。當新增到不同管線的是同一個例項時候,由於是單例,所以第一個連線會把單例的物件的added設定為了true,所以其他連線檢查時候發現沒有新增@Sharable註解並且當前added為true則會丟擲異常。

四、總結

正常情況下同一個ChannelHandler,的不同的例項會被新增到不同的Channel管理的管線裡面的,但是如果你需要全域性統計一些資訊,比如所有連線報錯次數(exceptionCaught)等,這時候你可能需要使用單例的ChannelHandler,需要注意的是這時候ChannelHandler上需要新增@Sharable註解。

最後、Java併發程式設計之美已經出版

加多

加多

高階 Java 攻城獅 at 阿里巴巴加多,目前就職於阿里巴巴,熱衷併發程式設計、ClassLoader,Spring等開源框架,分散式RPC框架dubbo,springcloud等;愛好音樂,運動。微信公眾號:技術原始積累。知識星球賬號:技術原始積累