• Netty网络框架学习笔记-17(客户端断线重连)


    Netty网络框架学习笔记-17(客户端断线重连_2022-06-27)

    使用网络编程, 就不可避免客户端存在, 断网, 设备断电, 导致客户端与服务端的连接中断, 在或者启动时候就失败了!

    所以需要有重连机制

    netty的重连本质上就是在调多一次 bootstrap.connect(remoteAddress).sync()

    1.0 最简单的固定间隔时间重连 (不建议使用)

    客户端连接成功后, 当前连接线程会阻塞,

    当客户端连接失败, 会抛出一个 ConnectException 异常, 当拦截到这个异常就开启重连

    public static void main(String[] args) {
            NioEventLoopGroup workerGroup = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            bootstrap = bootstrap.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                           //..........省略
                        }
                    });
    
            InetSocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 8888);
            // 重试次数
            int retryCount = 100;
        	// 一个条件死循环, 里面第一步会不断的连接服务端
            for (; ; ) {
                try {
                    ChannelFuture channelFuture = bootstrap.connect(remoteAddress).sync();
                    channelFuture.channel().closeFuture().sync();
                } catch (Exception e) {
                    // 当是连接异常时候, 继续循环
                    if (e instanceof ConnectException){
                        retryCount--;
                        log.error("NettyTcpClient-发生异常, 信息:", e);
                        ThreadUtil.sleep(15, TimeUnit.SECONDS);
                    }else {
                        break;
                    }
                }
                // 重试到达100次数则不在重试 , 或者可以自定义次数, 或者可以无限每隔15秒重试一次
                if (retryCount <= 0) {
                    break;
                }
            }
            // 关闭线程组
            workerGroup.shutdownGracefully();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    上面这种方法很简单, 也有缺点, 有可能客户端第一次启动, 连接地址写错了, 但确实有这个地址, 也可能会报 ConnectException 异常, 也会一直触发重连, 而且由于连接地址是错误的 , 所以会一直连接不上。 所以这种方法不建议使用

    如果非要使用, 得配置, 连接失败时候, 会通知开发人员, 例如: 钉钉、 电话、 邮件等通知

    2.0 建议使用方式,

    这个方式, 本质是已经在建立连接成功后, 使用netty提供的处理器事件, 当通道不活跃的时候触发的事件channelInactive(ChannelHandlerContext ctx) 里面进行重试连接。

    2.1.1 重试策略类RetryStrategy

    /**
     * @Author: ZhiHao
     * @Date: 2022/6/27 16:31
     * @Description:
     * @Versions 1.0
     **/
    @Slf4j
    public class RetryStrategy {
    
        // 启动引导类
        private Bootstrap bootstrap;
        // 最大重试次数
        private Integer retryMaxCount = Integer.MAX_VALUE;
        // 最长间隔重试时间
        private Integer retryMaxTime = 60;
        // 每次重试增加多少秒
        private Integer retryAddTime = 0;
        // 重试成功触发的事件
        private Consumer<Boolean> consumer;
        // 初始重试次数
        private int currentRetryCount = 0;
        // 初始重试间隔 1秒
        private long delay = 1L;
    
        public Bootstrap buildBootstrapAndReturnBootstrap(Bootstrap bootstrap) {
            this.bootstrap = bootstrap;
            return bootstrap;
        }
    
        public void setRetryMaxCount(Integer retryMaxCount) {
            this.retryMaxCount = retryMaxCount;
        }
    
        public void setRetryMaxTime(Integer retryMaxTime) {
            this.retryMaxTime = retryMaxTime;
        }
    
        public void setRetryAddTime(Integer retryAddTime) {
            this.retryAddTime = retryAddTime;
        }
    
        public void setConsumer(Consumer<Boolean> consumer) {
            this.consumer = consumer;
        }
    
        public void processRetryConnect(ChannelHandlerContext ctx) {
            if (Objects.isNull(ctx)){
                log.error("RetryStrategy===处理器都不存在!!!");
                // 关闭整个客户端, 是整个netty应用停止
                bootstrap.config().group().shutdownGracefully();
                return;
            }
            
            if (currentRetryCount >= retryMaxCount) {
                log.error("RetryStrategy===重试已达最大次数, 取消重试, 关闭客户端!!!");
                // 关闭整个客户端, 是整个netty应用停止
                bootstrap.config().group().shutdownGracefully();
                return;
            }
            
     		long delay = this.getDelay();
            EventLoop eventLoop = ctx.channel().eventLoop();
            eventLoop.schedule(() -> {
                try {
                    currentRetryCount++;
                    long delayTime = this.delay;
                    int count = this.currentRetryCount;
    
                    ChannelFuture channelFuture = bootstrap.connect();
                    channelFuture.addListener(future -> {
                        // 触发事件
                        boolean futureSuccess = future.isSuccess();
                        // 失败后重调递归调
                        if (!futureSuccess) {
                            this.processRetryConnect(ctx);
                        } else {
                            // 成功后重置次数和延迟时间
                            this.currentRetryCount = 0;
                            this.delay = 0L;
                            Optional.ofNullable(consumer).ifPresent(el -> el.accept(Boolean.TRUE));
                        }
                        log.info("===RetryStrategy===, 重连, 当前次数:{}, 当前延迟重试间隔:{}秒, 重试结果:{}", count, delay, futureSuccess);
                    });
                    channelFuture.sync();
                } catch (Exception e) {
                    log.error("NettyRetryClientHandle====重连失败, 原因: ", e);
                }
            }, delay, TimeUnit.SECONDS);
            // 传递给下一个处理器
            ctx.fireChannelInactive();
        }
    
        private long getDelay() {
         	if (delay >= retryMaxTime) {
                return retryMaxTime;
            }
            if (currentRetryCount != 0) {
                delay += retryAddTime;
            }
            return delay;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    2.1.2 自定义重试处理器

    @Slf4j
    public class NettyRetryClientHandle extends ChannelInboundHandlerAdapter {
    
        private RetryStrategy retryStrategy;
    
        public NettyRetryClientHandle(RetryStrategy retryStrategy) {
            this.retryStrategy = retryStrategy;
        }
    
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            log.error("NettyRetryClientHandle===触发通道不活跃, 进行重连!");
            retryStrategy.setConsumer(el->{
                log.info("NettyRetryClientHandle===重连成功,  触发做自己的事情!!!!!!!");
            });
            retryStrategy.processRetryConnect(ctx);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.1.3 客户端

    @Slf4j
    public class NettyRetryClientTwo {
    
        public static void main(String[] args) {
            NioEventLoopGroup workerGroup = new NioEventLoopGroup();
            RetryStrategy retryStrategy = new RetryStrategy();
            retryStrategy.setRetryAddTime(1);
            retryStrategy.setRetryMaxCount(3);
            Bootstrap bootstrap = retryStrategy.buildBootstrapAndReturnBootstrap(new Bootstrap());
    
            bootstrap = bootstrap.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new NettyTcpClientHandle());
                            // 添加自定义重试连接处理器
                            pipeline.addLast(new NettyRetryClientHandle(retryStrategy));
                        }
                    });
    
            bootstrap.remoteAddress(new InetSocketAddress("127.0.0.1", 8888));
            //bootstrap.remoteAddress(new InetSocketAddress("47.119.137.321", 3306));
            try {
                ChannelFuture channelFuture = bootstrap.connect();
                // 添加监听, 最初启动时候是否成功!
                channelFuture.addListener(el->{
                    // 失败则进行重试!
                    if (!el.isSuccess()) {
                        ChannelHandlerContext context = channelFuture.channel().pipeline().context(NettyRetryClientHandle.class);
                        retryStrategy.processRetryConnect(context);
                    }else {
                        log.error("NettyTcpClient-启动成功了!!!");
                    }
                });
    
                channelFuture.sync().channel().closeFuture().sync();
            } catch (Exception e) {
                log.error("NettyTcpClient-发生异常, 信息:", e);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    2.1 进行测试, 客户端一启动就报错

    ##  一直重试到最大次数都没有成功, 关闭客户端!
    18:20:03.786 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientTwo - NettyTcpClient-启动就连接失败了, 进行重试!!!
    18:20:03.786 [main] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientTwo - NettyTcpClient-发生异常, 信息:
    io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
    Caused by: java.net.ConnectException: Connection refused: no further information
    18:20:06.833 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
    io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
    Caused by: java.net.ConnectException: Connection refused: no further information
    18:20:06.833 [nioEventLoopGroup-2-2] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:1, 当前延迟重试间隔:1秒, 重试结果:false
    18:20:10.886 [nioEventLoopGroup-2-3] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:2, 当前延迟重试间隔:2秒, 重试结果:false
    18:20:10.886 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
    io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
    Caused by: java.net.ConnectException: Connection refused: no further information
    18:20:15.947 [nioEventLoopGroup-2-4] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - RetryStrategy===重试已达最大次数, 取消重试, 关闭客户端!!!
    18:20:15.947 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
    io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
    Caused by: java.net.ConnectException: Connection refused: no further information
    18:20:15.948 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:3, 当前延迟重试间隔:3秒, 重试结果:false
    ### -----------------------------------------------------------------------------------------------------
    ###  重试成功!
    18:29:50.378 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientTwo - NettyTcpClient-启动就连接失败了, 进行重试!!!
    18:29:50.379 [main] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientTwo - NettyTcpClient-发生异常, 信息:
    io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
    Caused by: java.net.ConnectException: Connection refused: no further information
    18:29:53.437 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
    io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
    Caused by: java.net.ConnectException: Connection refused: no further information
    18:29:53.437 [nioEventLoopGroup-2-2] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:1, 当前延迟重试间隔:1秒, 重试结果:false
    18:29:57.488 [nioEventLoopGroup-2-3] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:2, 当前延迟重试间隔:2秒, 重试结果:false
    18:29:57.488 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
    io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
    Caused by: java.net.ConnectException: Connection refused: no further information
    18:30:01.007 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:3, 当前延迟重试间隔:3秒, 重试结果:true
    18:30:01.021 [nioEventLoopGroup-2-4] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 4096
    18:30:01.021 [nioEventLoopGroup-2-4] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
    18:30:01.021 [nioEventLoopGroup-2-4] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.chunkSize: 32
    18:30:01.030 [nioEventLoopGroup-2-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
    18:30:01.030 [nioEventLoopGroup-2-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
    18:30:01.031 [nioEventLoopGroup-2-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@a6737b0
    18:30:01.089 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到信息:74e0c2f2-c993-4831-bc62-31f709946538b508fa6f-a884-471c-994b-85179bb21a7128d57d14-a5bc-4205-8476-35d511bcaffa8096b5d0-5f7a-411a-b96d-46a1405c992823f0090e-61d0-423c-80c6-26afc46c359f
    18:30:01.089 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到多少次信息:1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    2.2 进行测试, 网络波动或者中途断连

    18:33:11.642 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientTwo - NettyTcpClient-启动成功了!!!
    18:33:11.644 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到信息:2e0ad20d-81a4-4a76-8065-259a57e247ff5e587c61-c96a-45d3-81d5-dd69dcd94f1457700452-3074-4bd6-be94-b862842a098104963210-7f46-40eb-9fb1-00a7d02918aa6e43e648-e965-474f-a319-bdf155f4a7bc
    18:33:11.644 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到多少次信息:1
    18:33:35.399 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.NettyRetryClientHandle - NettyRetryClientHandle===触发通道不活跃, 进行重连!
    18:33:38.451 [nioEventLoopGroup-2-2] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:1, 当前延迟重试间隔:1秒, 重试结果:false
    18:33:38.452 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
    io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
    Caused by: java.net.ConnectException: Connection refused: no further information
    18:33:42.512 [nioEventLoopGroup-2-3] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:2, 当前延迟重试间隔:2秒, 重试结果:false
    18:33:42.512 [nioEventLoopGroup-2-1] ERROR com.zhihao.netty.clientRetryConnect.RetryStrategy - NettyRetryClientHandle====重连失败, 原因: 
    io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8888
    Caused by: java.net.ConnectException: Connection refused: no further information
    18:33:46.524 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.clientRetryConnect.NettyRetryClientHandle - NettyRetryClientHandle===重连成功,  触发做自己的事情!!!!!!!
    18:33:46.525 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.clientRetryConnect.RetryStrategy - ===RetryStrategy===, 当前次数:3, 当前延迟重试间隔:3秒, 重试结果:true
    18:33:46.584 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到信息:fe3de0ab-6c65-45bd-8852-0f92805bb23d
    18:33:46.584 [nioEventLoopGroup-2-4] INFO com.zhihao.netty.tcp.NettyTcpClientHandle - NettyTcpClientHandle-读取到多少次信息:8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    总结:

    第一种方式使用场景有限!

    第二种方式, 可以自定义扩展的情况更全一点!!!

    1

  • 相关阅读:
    Java 19 正式发布,改善多线程、并发编程难度
    Go语言 映射(Map)
    Python之并发编程(线程)
    uniapp封装loading 的动画动态加载
    C#代码审计实战+前置知识
    全栈技术面试十问(中英双语)
    中电金信:深度解析|数字化营销运营体系搭建
    Django的查询所有,根据用户名查询,增加用户操作
    引入成熟的Pytest自动化测试框架
    论文答辩小技巧!
  • 原文地址:https://blog.csdn.net/weixin_44600430/article/details/125489553