一、問題描述
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(1); 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可以滿足我們的需求,但是有些業務場景(例如:返回結果時間不確定)需要使用這種異步的連接池,在正確的業務場景下選擇正確的解決方案才是王道哦。