美文网首页Netty技术
netty应用中一次DefaultChannelPromise异

netty应用中一次DefaultChannelPromise异

作者: 简单是美美 | 来源:发表于2019-12-18 11:20 被阅读0次

    1. 问题的产生与现象

      近日在开发一个基于netty的网络应用中,需要考虑一个channel断链后重连的场景。在ChannelInboundHandlerAdapter中,当channelInactive函数被触发时提示断链,并在channelInactive函数中执行断链重连函数reConnectChannel。
      reConnectChannel在指定次数与间隔内执行重连操作,重连操作的代码大致如下:

    public boolean reConnectChannel(LoginUserInfo lui, boolean isThirdParty) throws Exception {
    ... ...
    Bootstrap bootstrap = ncs.getNewBootstrap();
            ChannelFuture future = bootstrap.connect(lui.getSingalServerIp(), lui.getSingalServerPort()).sync();
            Channel channel = future.channel();
            lui.setSignalChannel(channel);
            log.info("create channel:channel={}",channel);
    ... ...
    }
    

      代码很简单,就是每次生成一个新的Bootstrap(NioSocketChannel),然后用这个新的Bootstrap做connect操作去连接服务器。其中getNewBootstrap函数代码如下:

    public Bootstrap getNewBootstrap() {
            Bootstrap bs = new Bootstrap();
            bs.group(connectGroup).channel(NioSocketChannel.class);
            bs.option(ChannelOption.TCP_NODELAY, true);
            bs.option(ChannelOption.SO_KEEPALIVE, true);
            bs.option(ChannelOption.SO_REUSEADDR, true);
            bs.option(ChannelOption.SO_SNDBUF, 128);
            bs.handler(clientChannelInitializer);
            return bs;
        }
    

      可以看到,每个新生成的Bootstrap注册到一个connectGroup(线程数为10)中的某个线程,由这个线程对该Bootstrap(NioSocketChannel)进行事件监听。
      在执行过程,打开netty的debug打印,可以看到一次正常重连(没连上)的打印如下:

    2019-12-18 10:09:43.372 DEBUG 16380 --- [nioEventLoopGroup-2-4] io.netty.handler.logging.LoggingHandler  : [id: 0xf00db0ac] REGISTERED
    2019-12-18 10:09:43.373 DEBUG 16380 --- [nioEventLoopGroup-2-4] io.netty.handler.logging.LoggingHandler  : [id: 0xf00db0ac] CONNECT: /172.16.249.205:9907
    2019-12-18 10:09:43.374 DEBUG 16380 --- [nioEventLoopGroup-2-4] io.netty.handler.logging.LoggingHandler  : [id: 0xf00db0ac] CLOSE
    2019-12-18 10:09:43.375 DEBUG 16380 --- [nioEventLoopGroup-2-4] io.netty.handler.logging.LoggingHandler  : [id: 0xf00db0ac] UNREGISTERED
    

      而一次异常重连的打印如下:

    2019-12-18 10:42:42.889 DEBUG 12940 --- [nioEventLoopGroup-2-1] io.netty.handler.logging.LoggingHandler  : [id: 0xc120d598] REGISTERED
    2019-12-18 10:42:42.889 ERROR 12940 --- [nioEventLoopGroup-2-1] c.k.v.o.service.pojo.LoginUserInfo       : channel reconnect throw exception:DefaultChannelPromise@447aeba5(incomplete)
    

      在10次重连中会出现一到两次DefaultChannelPromise的异常。产生的现象就是当网络恢复之后,本应只有一个Bootstrap(channel)重连成功,但之前在断链重连中抛出异常的Bootstrap(如上例中的[id: 0xc120d598])也重连成功了。

    2 问题分析

      分析上面的问题与现象,可以发现抛出异常的Bootstrap所执行的线程(如上例中nioEventLoopGroup-2-1)与执行reConnectChannel的线程是同一个。
      我们知道在NIO编程中,一个NioSocketChannel(Bootstrap)被注册到一个select线程中(connectGroup中的某个线程),由这个select线程监听这个NioSocketChannel的事件,一个select线程可能监听处理多个NioSocketChannel的事件,并调用事件响应函数,这是一个序列化的过程,即只有处理完这个NioSocketChannel的监听到的事件,才会处理下一个NioSocketChannel的事件。
      我们在一个NioSocketChannel的断链事件处理中新建了一个NioSocketChannel去进行重连,如果这个新的NioSocketChannel和原断链的NioSocketChannel被同一个select线程所监听,而我们又使用bootstrap.connect().sync()在那同步连接,则会出现死锁阻塞:sync()需要select线程监听处理到连接事件才退出,而select线程监听处理连接事件则需要调用sync()的channelInactive函数先退出。
      netty检测到这种死锁条件,因而抛出DefaultChannelPromise异常,而该NioSocketChannel未被CLOSE和UNREGISTERED,因而在网络恢复后又重新进行了连接。

    3 问题的解决

       如果继续使用bootstrap.connect().sync()方法,则需要保证执行bootstrap.connect().sync()方法的线程与bootstrap所注册的select线程不为同一线程。可将bootstrap.connect().sync()函数在一新建的线程中处理。
       如果不使用bootstrap.connect().sync()方法,则可改写为bootstrap.connect().addListener()方法实现异步等待连接事件。

    相关文章

      网友评论

        本文标题:netty应用中一次DefaultChannelPromise异

        本文链接:https://www.haomeiwen.com/subject/tyvynctx.html