天天看點

你真的了解Netty中@Sharable?一、前言二、Netty基礎快速回顧三、ChannelHandler四、總結最後、Java并發程式設計之美已經出版

一、前言

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 源碼裡面的一個圖:
你真的了解Netty中@Sharable?一、前言二、Netty基礎快速回顧三、ChannelHandler四、總結最後、Java并發程式設計之美已經出版

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

三、ChannelHandler

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

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

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

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

這樣當伺服器接受到完成TCP三向交握連結的時候給目前完成握手的Channel通道建立一個ChannelPipeline,并且添加同一個EchoServerHandler的執行個體到對應管道。

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

這是因為我們的EchoServerHandler沒有被添加@Sharable注解,現在添加如下:

再次運作就OK了。

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

可知當添加到不同管線的是不同的執行個體時候,不同連接配接在檢查時候h.added總是傳回的false,是以不會抛出異常。當添加到不同管線的是同一個執行個體時候,由于是單例,是以第一個連接配接會把單例的對象的added設定為了true,是以其他連接配接檢查時候發現沒有添加@Sharable注解并且目前added為true則會抛出異常。

四、總結

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

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

京東連結:

https://item.jd.com/12450812.html

天貓連結:

https://detail.tmall.com/item.htm?spm=a220m.1000858.1000725.61.59a73c85cVE3Sk&id=579705310959&user_id=1932014659&cat_id=2&is_b=1&rn=8030c6abc3a06ef8b8e63bc1badcbbb2