教你正確地利用Netty建立連線池

K太狼發表於2016-04-09

一、問題描述

Netty是最近非常流行的高效能非同步通訊框架,相對於Java原生的NIO介面,Netty封裝後的非同步通訊機制要簡單很多。

但是小K最近發現並不是所有開發人員在使用的過程中都瞭解其內部實現機制,而是照著葫蘆畫瓢。

網上簡單搜尋下,在客戶端使用Netty建立連線池的文章也是比較少。今天小K給大家簡單介紹下使用Netty建立連線池的方法。

首先我們來看下Netty官方給出的客戶端sample例項:

  //建立一個EventLoopGroup,可以簡單認為是Netty框架下的執行緒池,預設最大執行緒數量是處理器數量的2倍
  EventLoopGroup group = new NioEventLoopGroup();
  try {
//Netty建立連線的輔助類
      Bootstrap b = new Bootstrap();
//配置屬性,向pipeline新增handler
      b.group(group)
       .channel(NioSocketChannel.class)
       .option(ChannelOption.TCP_NODELAY, true)
       .handler(new ChannelInitializer<SocketChannel>() {
           @Override
           public void initChannel(SocketChannel ch) throws Exception {
               ChannelPipeline p = ch.pipeline();
               if (sslCtx != null) {
                   p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
               }
               //p.addLast(new LoggingHandler(LogLevel.INFO));
               p.addLast(new EchoClientHandler());
           }
       });

      //啟動建立連線
      ChannelFuture f = b.connect(HOST, PORT).sync();

      //block直到連線被關閉
      f.channel().closeFuture().sync();

很簡單?沒錯,確實如此。那麼現在問題來了,如果你現在需要連線100個伺服器,你會怎麼做呢?

下面這樣處理怎麼樣呢?我們在外層加了一個for迴圈

for(Host host : hosts){
          //建立一個EventLoopGroup,可以簡單認為是Netty框架下的執行緒池,預設執行緒數量是處理器數量的2倍
          EventLoopGroup group = new NioEventLoopGroup();
          try {
        //Netty建立連線的輔助類
              Bootstrap b = new Bootstrap();
        //配置屬性,向pipeline新增handler
              b.group(group)
               .channel(NioSocketChannel.class)
               .option(ChannelOption.TCP_NODELAY, true)
               .handler(new ChannelInitializer<SocketChannel>() {
                   @Override
                   public void initChannel(SocketChannel ch) throws Exception {
                       ChannelPipeline p = ch.pipeline();
                       if (sslCtx != null) {
                           p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                       }
                       //p.addLast(new LoggingHandler(LogLevel.INFO));
                       p.addLast(new EchoClientHandler());
                   }
               });

              //啟動建立連線
              ChannelFuture f = b.connect(HOST, PORT).sync();

              //block直到連線被關閉
              f.channel().closeFuture().sync();
}

問題很明顯,如果每一個channel都對應一個NIOEventLoopGroup,那麼我們實際上構建了一個connection:thread = 1:1的模型,隨著連線數不斷地擴大,執行緒膨脹的問題就會突顯出來。

二、問題解決

那麼如何避免執行緒膨脹的問題呢?很簡單,我們只要稍微修改下上面的程式碼就可以了。

NioEventLoopGroup group = new NioEventLoopGroup();
    Bootstrap b = new Bootstrap();

    try {
      b.group(group)
       .channel(NioSocketChannel.class)
       .option(ChannelOption.TCP_NODELAY, true)
       .handler(new ChannelInitializer<SocketChannel>() {
           @Override
           public void initChannel(SocketChannel ch) throws Exception {
               ChannelPipeline p = ch.pipeline();
               if (sslCtx != null) {
                   p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
               }
               //p.addLast(new LoggingHandler(LogLevel.INFO));
                           p.addLast(new EchoClientHandler());
                       }
         });
     for(Host HOST : Hosts){
         ChannelFuture f = b.connect(HOST, PORT).sync();
     }

在上面的程式碼中,我們使用同一個Bootstrap建立了多個連線,從而使連線共享了一個NioEventLoopGroup,避免了執行緒膨脹的問題。

問題就這樣解決了嗎?NO,還遠遠沒有結束哦,那麼問題又來了。

1、如果希望每個連線能夠使用不同的Handler怎麼辦?

2、每個連線如何能夠再次複用,避免重複建立channel?

為了能夠建立非同步操作的連線池我們需要實現如下的模型。

為了能夠方便地建立一個非同步操作的連線池,我們會使用到FixedChannelPool(不瞭解的同學麻煩Google一下吧,(⌒_⌒))

其虛擬碼如下(具體的程式碼實現結構還是留給讀者自己思考吧):

Bootstrap b = new Bootstrap().channel(NioSocketChannel.class).group(
new NioEventLoopGroup());

//自定義的channelpoolhandler
ChannelPoolHandler handler = new ChannelPoolHandler();

//建立一個FixedChannelPool
FixedChannelPool pool = new FixedChannelPool(bootstrap, handler);

//從channelpool中獲取連線或者建立新的連線,並新增listener
pool.acquire.addlistener();

三、總結

通常情況下,我們並不需要使用Netty建立連線池,common pool可以滿足我們的需求,但是有些業務場景(例如:返回結果時間不確定)需要使用這種非同步的連線池,在正確的業務場景下選擇正確的解決方案才是王道哦。

相關文章