Netty生产级的心跳和重连机制
今天研究的是,心跳和重连,虽然这次是大神写的代码,但是万变不离其宗,我们先回顾一下Netty应用心跳和重连的整个过程:
1)客户端连接服务端
2)在客户端的的ChannelPipeline中加入一个比较特殊的IdleStateHandler,设置一下客户端的写空闲时间,例如5s
3)当客户端的所有ChannelHandler中4s内没有write事件,则会触发userEventTriggered方法(上文介绍过)
4)我们在客户端的userEventTriggered中对应的触发事件下发送一个心跳包给服务端,检测服务端是否还存活,防止服务端已经宕机,客户端还不知道
5)同样,服务端要对心跳包做出响应,其实给客户端最好的回复就是“不回复”,这样可以服务端的压力,假如有10w个空闲Idle的连接,那么服务端光发送心跳回复,则也是费事的事情,那么怎么才能告诉客户端它还活着呢,其实很简单,因为5s服务端都会收到来自客户端的心跳信息,那么如果10秒内收不到,服务端可以认为客户端挂了,可以close链路
6)加入服务端因为什么因素导致宕机的话,就会关闭所有的链路链接,所以作为客户端要做的事情就是短线重连
以上描述的就是整个心跳和重连的整个过程,虽然很简单,上一篇blog也写了一个Demo,简单地做了一下上述功能
要写工业级的Netty心跳重连的代码,需要解决一下几个问题:
1)ChannelPipeline中的ChannelHandlers的维护,首次连接和重连都需要对ChannelHandlers进行管理
2)重连对象的管理,也就是bootstrap对象的管理
3)重连机制编写
完整的代码:https://github.com/BazingaLyn/netty-study/tree/master/src/main/java/com/lyncc/netty/idle
下面我们就看大神是如何解决这些问题的,首先先定义一个接口ChannelHandlerHolder,用来保管ChannelPipeline中的Handlers的
- package com.lyncc.netty.idle;
- import io.netty.channel.ChannelHandler;
- /**
- *
- * 客户端的ChannelHandler集合,由子类实现,这样做的好处:
- * 继承这个接口的所有子类可以很方便地获取ChannelPipeline中的Handlers
- * 获取到handlers之后方便ChannelPipeline中的handler的初始化和在重连的时候也能很方便
- * 地获取所有的handlers
- */
- public interface ChannelHandlerHolder {
- ChannelHandler[] handlers();
- }
我们再来编写我们熟悉的服务端的ServerBootstrap的编写:
HeartBeatServer.java
- package com.lyncc.netty.idle;
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- import io.netty.handler.codec.string.StringDecoder;
- import io.netty.handler.codec.string.StringEncoder;
- import io.netty.handler.logging.LogLevel;
- import io.netty.handler.logging.LoggingHandler;
- import io.netty.handler.timeout.IdleStateHandler;
- import java.net.InetSocketAddress;
- import java.util.concurrent.TimeUnit;
- public class HeartBeatServer {
- private final AcceptorIdleStateTrigger idleStateTrigger = new AcceptorIdleStateTrigger();
- private int port;
- public HeartBeatServer(int port) {
- this.port = port;
- }
- public void start() {
- EventLoopGroup bossGroup = new NioEventLoopGroup(1);
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap sbs = new ServerBootstrap().group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO))
- .localAddress(new InetSocketAddress(port)).childHandler(new ChannelInitializer<SocketChannel>() {
- protected void initChannel(SocketChannel ch) throws Exception {
- ch.pipeline().addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
- ch.pipeline().addLast(idleStateTrigger);
- ch.pipeline().addLast("decoder", new StringDecoder());
- ch.pipeline().addLast("encoder", new StringEncoder());
- ch.pipeline().addLast(new HeartBeatServerHandler());
- };
- }).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);
- // 绑定端口,开始接收进来的连接
- ChannelFuture future = sbs.bind(port).sync();
- System.out.println("Server start listen at " + port);
- future.channel().closeFuture().sync();
- } catch (Exception e) {
- bossGroup.shutdownGracefully();
- workerGroup.shutdownGracefully();
- }
- }
- public static void main(String[] args) throws Exception {
- int port;
- if (args.length > 0) {
- port = Integer.parseInt(args[0]);
- } else {
- port = 8080;
- }
- new HeartBeatServer(port).start();
- }
- }
单独写一个AcceptorIdleStateTrigger,其实也是继承ChannelInboundHandlerAdapter,重写userEventTriggered方法,因为客户端是write,那么服务端自然是read,设置的状态就是IdleState.READER_IDLE,源码如下:
- package com.lyncc.netty.idle;
- import io.netty.channel.ChannelHandler;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.ChannelInboundHandlerAdapter;
- import io.netty.handler.timeout.IdleState;
- import io.netty.handler.timeout.IdleStateEvent;
- @ChannelHandler.Sharable
- public class AcceptorIdleStateTrigger extends ChannelInboundHandlerAdapter {
- @Override
- public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
- if (evt instanceof IdleStateEvent) {
- IdleState state = ((IdleStateEvent) evt).state();
- if (state == IdleState.READER_IDLE) {
- throw new Exception("idle exception");
- }
- } else {
- super.userEventTriggered(ctx, evt);
- }
- }
- }
HeartBeatServerHandler就是一个很简单的自定义的Handler,不是重点:
- package com.lyncc.netty.idle;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.ChannelInboundHandlerAdapter;
- public class HeartBeatServerHandler extends ChannelInboundHandlerAdapter {
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- System.out.println("server channelRead..");
- System.out.println(ctx.channel().remoteAddress() + "->Server :" + msg.toString());
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- cause.printStackTrace();
- ctx.close();
- }
- }
接下来就是重点,我们需要写一个类,这个类可以去观察链路是否断了,如果断了,进行循环的断线重连操作,ConnectionWatchdog,顾名思义,链路检测狗,我们先看完整代码:
- package com.lyncc.netty.idle;
- import io.netty.bootstrap.Bootstrap;
- import io.netty.channel.Channel;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelFutureListener;
- import io.netty.channel.ChannelHandler.Sharable;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.ChannelInboundHandlerAdapter;
- import io.netty.channel.ChannelInitializer;
- import io.netty.util.Timeout;
- import io.netty.util.Timer;
- import io.netty.util.TimerTask;
- import java.util.concurrent.TimeUnit;
- /**
- *
- * 重连检测狗,当发现当前的链路不稳定关闭之后,进行12次重连
- */
- @Sharable
- public abstract class ConnectionWatchdog extends ChannelInboundHandlerAdapter implements TimerTask ,ChannelHandlerHolder{
- private final Bootstrap bootstrap;
- private final Timer timer;
- private final int port;
- private final String host;
- private volatile boolean reconnect = true;
- private int attempts;
- public ConnectionWatchdog(Bootstrap bootstrap, Timer timer, int port,String host, boolean reconnect) {
- this.bootstrap = bootstrap;
- this.timer = timer;
- this.port = port;
- this.host = host;
- this.reconnect = reconnect;
- }
- /**
- * channel链路每次active的时候,将其连接的次数重新☞ 0
- */
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- System.out.println("当前链路已经激活了,重连尝试次数重新置为0");
- attempts = 0;
- ctx.fireChannelActive();
- }
- @Override
- public void channelInactive(ChannelHandlerContext ctx) throws Exception {
- System.out.println("链接关闭");
- if(reconnect){
- System.out.println("链接关闭,将进行重连");
- if (attempts < 12) {
- attempts++;
- //重连的间隔时间会越来越长
- int timeout = 2 << attempts;
- timer.newTimeout(this, timeout, TimeUnit.MILLISECONDS);
- }
- }
- ctx.fireChannelInactive();
- }
- public void run(Timeout timeout) throws Exception {
- ChannelFuture future;
- //bootstrap已经初始化好了,只需要将handler填入就可以了
- synchronized (bootstrap) {
- bootstrap.handler(new ChannelInitializer<Channel>() {
- @Override
- protected void initChannel(Channel ch) throws Exception {
- ch.pipeline().addLast(handlers());
- }
- });
- future = bootstrap.connect(host,port);
- }
- //future对象
- future.addListener(new ChannelFutureListener() {
- public void operationComplete(ChannelFuture f) throws Exception {
- boolean succeed = f.isSuccess();
- //如果重连失败,则调用ChannelInactive方法,再次出发重连事件,一直尝试12次,如果失败则不再重连
- if (!succeed) {
- System.out.println("重连失败");
- f.channel().pipeline().fireChannelInactive();
- }else{
- System.out.println("重连成功");
- }
- }
- });
- }
- }
稍微分析一下:
1)继承了ChannelInboundHandlerAdapter,说明它也是Handler,也对,作为一个检测对象,肯定会放在链路中,否则怎么检测
2)实现了2个接口,TimeTask,ChannelHandlerHolder
①TimeTask,我们就要写run方法,这应该是一个定时任务,这个定时任务做的事情应该是重连的工作
②ChannelHandlerHolder的接口,这个接口我们刚才说过是维护的所有的Handlers,因为在重连的时候需要获取Handlers
3)bootstrap对象,重连的时候依旧需要这个对象
4)当链路断开的时候会触发channelInactive这个方法,也就说触发重连的导火索是从这边开始的
好了,我们这边再写次核心的HeartBeatsClient的代码:
- package com.lyncc.netty.idle;
- import io.netty.bootstrap.Bootstrap;
- import io.netty.channel.Channel;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelHandler;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.nio.NioSocketChannel;
- import io.netty.handler.codec.string.StringDecoder;
- import io.netty.handler.codec.string.StringEncoder;
- import io.netty.handler.logging.LogLevel;
- import io.netty.handler.logging.LoggingHandler;
- import io.netty.handler.timeout.IdleStateHandler;
- import io.netty.util.HashedWheelTimer;
- import java.util.concurrent.TimeUnit;
- public class HeartBeatsClient {
- protected final HashedWheelTimer timer = new HashedWheelTimer();
- private Bootstrap boot;
- private final ConnectorIdleStateTrigger idleStateTrigger = new ConnectorIdleStateTrigger();
- public void connect(int port, String host) throws Exception {
- EventLoopGroup group = new NioEventLoopGroup();
- boot = new Bootstrap();
- boot.group(group).channel(NioSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO));
- final ConnectionWatchdog watchdog = new ConnectionWatchdog(boot, timer, port,host, true) {
- public ChannelHandler[] handlers() {
- return new ChannelHandler[] {
- this,
- new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS),
- idleStateTrigger,
- new StringDecoder(),
- new StringEncoder(),
- new HeartBeatClientHandler()
- };
- }
- };
- ChannelFuture future;
- //进行连接
- try {
- synchronized (boot) {
- boot.handler(new ChannelInitializer<Channel>() {
- //初始化channel
- @Override
- protected void initChannel(Channel ch) throws Exception {
- ch.pipeline().addLast(watchdog.handlers());
- }
- });
- future = boot.connect(host,port);
- }
- // 以下代码在synchronized同步块外面是安全的
- future.sync();
- } catch (Throwable t) {
- throw new Exception("connects to fails", t);
- }
- }
- /**
- * @param args
- * @throws Exception
- */
- public static void main(String[] args) throws Exception {
- int port = 8080;
- if (args != null && args.length > 0) {
- try {
- port = Integer.valueOf(args[0]);
- } catch (NumberFormatException e) {
- // 采用默认值
- }
- }
- new HeartBeatsClient().connect(port, "127.0.0.1");
- }
- }
也稍微说明一下:
1)创建了ConnectionWatchdog对象,自然要实现handlers方法
2)初始化好bootstrap对象
3)4秒内没有写操作,进行心跳触发,也就是IdleStateHandler这个方法
最后ConnectorIdleStateTrigger这个类
- package com.lyncc.netty.idle;
- import io.netty.buffer.ByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.ChannelHandler.Sharable;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.ChannelInboundHandlerAdapter;
- import io.netty.handler.timeout.IdleState;
- import io.netty.handler.timeout.IdleStateEvent;
- import io.netty.util.CharsetUtil;
- @Sharable
- public class ConnectorIdleStateTrigger extends ChannelInboundHandlerAdapter {
- private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Heartbeat",
- CharsetUtil.UTF_8));
- @Override
- public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
- if (evt instanceof IdleStateEvent) {
- IdleState state = ((IdleStateEvent) evt).state();
- if (state == IdleState.WRITER_IDLE) {
- // write heartbeat to server
- ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate());
- }
- } else {
- super.userEventTriggered(ctx, evt);
- }
- }
- }
HeartBeatClientHandler.java(不是重点)
- package com.lyncc.netty.idle;
- import io.netty.channel.ChannelHandler.Sharable;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.ChannelInboundHandlerAdapter;
- import io.netty.util.ReferenceCountUtil;
- import java.util.Date;
- @Sharable
- public class HeartBeatClientHandler extends ChannelInboundHandlerAdapter {
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- System.out.println("激活时间是:"+new Date());
- System.out.println("HeartBeatClientHandler channelActive");
- ctx.fireChannelActive();
- }
- @Override
- public void channelInactive(ChannelHandlerContext ctx) throws Exception {
- System.out.println("停止时间是:"+new Date());
- System.out.println("HeartBeatClientHandler channelInactive");
- }
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- String message = (String) msg;
- System.out.println(message);
- if (message.equals("Heartbeat")) {
- ctx.write("has read message from server");
- ctx.flush();
- }
- ReferenceCountUtil.release(msg);
- }
- }
好了,到此为止,所有的代码都贴完了,我们做一个简单的测试,按照常理,如果不出任何状况的话,客户端4秒发送心跳,服务端5秒才验证是不会断连的,所以我们在启动之后,关闭服务端,然后再次重启服务端
首先启动服务端,控制台如下:
启动客户端,控制台如下:
客户端启动之后,服务端的控制台:
关闭服务端后,客户端控制台:
重启启动服务端:
重连成功~
Netty生产级的心跳和重连机制的更多相关文章
- Netty 之 Netty生产级的心跳和重连机制
https://blog.csdn.net/z69183787/article/details/52625095 最近工作比较忙,但闲暇之余还是看了阿里的冯家春(fengjiachun)的github ...
- 正确理解IM长连接的心跳及重连机制,并动手实现(有完整IM源码)
1.引言 说道“心跳”这个词大家都不陌生,当然不是指男女之间的心跳,而是和长连接相关的.顾名思义就是证明是否还活着的依据. 什么场景下需要心跳呢?目前我们接触到的大多是一些基于长连接的应用需要心跳来“ ...
- 从零开始实现简单 RPC 框架 9:网络通信之心跳与重连机制
一.心跳 什么是心跳 在 TPC 中,客户端和服务端建立连接之后,需要定期发送数据包,来通知对方自己还在线,以确保 TPC 连接的有效性.如果一个连接长时间没有心跳,需要及时断开,否则服务端会维护很多 ...
- 理解WebSocket心跳及重连机制(五)
理解WebSocket心跳及重连机制 在使用websocket的过程中,有时候会遇到网络断开的情况,但是在网络断开的时候服务器端并没有触发onclose的事件.这样会有:服务器会继续向客户端发送多余的 ...
- 简易RPC框架-心跳与重连机制
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- 基于netty实现的长连接,心跳机制及重连机制
技术:maven3.0.5 + netty4.1.33 + jdk1.8 概述 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...
- Netty学习总结(3)——Netty百万级推送服务
1. 背景 1.1. 话题来源 最近很多从事移动互联网和物联网开发的同学给我发邮件或者微博私信我,咨询推送服务相关的问题.问题五花八门,在帮助大家答疑解惑的过程中,我也对问题进行了总结,大概可以归纳为 ...
- Netty简单的重连机制
其实重连机制并不是多么多高深的技术,其实就是一个在客户端做一个简单的判断,如果连接断了,那么就重新调用连接服务端的代码 当然,我们重连的动作肯定是发生在断连之后发生的,我们可以在上篇的心跳机制的基础上 ...
- 微信自研生产级paxos类库PhxPaxos实现原理介绍
转载自: http://mp.weixin.qq.com/s?__biz=MzI4NDMyNTU2Mw==&mid=2247483695&idx=1&sn=91ea4229 ...
随机推荐
- Linux服务器运行环境搭建(四)——Tomcat安装
官网地址:http://tomcat.apache.org 官网下载地址(Tomcat6):http://tomcat.apache.org/download-60.cgi,Windows下载wind ...
- BZOJ3675 Apio2014 序列分割 【斜率优化】
Description 小H最近迷上了一个分隔序列的游戏.在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列.为了得到k+1个子序列,小H需要重复k次以下的步骤: 1.小H首 ...
- LOJ2613 NOIP2013 华容道 【最短路】*
LOJ2613 NOIP2013 华容道 LINK 这是个好题,具体题意比较麻烦可以直接看LINK中的链接 然后考虑我们可能的移动方式 首先我们需要把白块移动到需要移动块S的附近(附近四格) 然后我们 ...
- BZOJ1131 POI2008 Sta 【树形DP】
BZOJ1131 POI2008 Sta Description 给出一个N个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大 Input 给出一个数字N,代表有N个点.N<=10 ...
- IIS并发瓶颈线程数的限制
.NET线程池最大线程数的限制-记一次IIS并发瓶颈 https://www.cnblogs.com/7rhythm/p/9964543.html .NET ThreadPool 最大线程数的限制 I ...
- FastAdmin 如何查看 ICON 名字?
FastAdmin 如何查看 ICON 名字? 群问题: [A货] ★^猪大胖-镇江 我想问问大家谁知道如何快速查找icon的name 每次我都要编辑权限那里去复制 [吐槽]孤狼-海口 2018/4/ ...
- bootstrap常用模态框
<!-- 触发器(button) --> <button class="btn btn-xs btn-success" data-toggle="mod ...
- laravel里面的控制器笔记
看了下教程,总结了下,大概分两种 一般的controller restful的controller 单独绑定action的route为 Route::get('user/{id}', 'UserCon ...
- java web----刷新页面的程序 (重复包括)
<%@ page language="java" import="java.util.*" pageEncoding="gb2312" ...
- linux下进程cpu占用过高问题定位方法
背景 记得前段时间,同事说他们测试环境的服务器cpu使用率一直处于100%,本地又没有什么接口调用,为什么会这样?cpu使用率居高不下,自然是有某些线程一直占用着cpu资源,那又如何查看占用cpu较高 ...