谈到RPC肯定绕不开TCP通信,而主流的RPC框架都依赖于Netty等通信框架,这时候我们还要考虑是使用长连接还是短连接:
一、Dubbo心跳设计分析
1.1 前言
谈到RPC肯定绕不开TCP通信,而主流的RPC框架都依赖于Netty等通信框架,这时候我们还要考虑是使用长连接还是短连接:
短连接:每次通信结束后关闭连接,下次通信需要重新创建连接;优点就是无需管理连接,无需保活连接;
长连接:每次通信结束不关闭连接,连接可以复用,保证了性能;缺点就是连接需要统一管理,并且需要保活;
主流的RPC框架都会追求性能选择使用长连接,所以如何保活连接就是一个重要的话题,也是本文的主题,下面会重点介绍一些保活策略;
1.2 为什么需要保活
上面介绍的长连接、短连接并不是TCP提供的功能,所以长连接是需要应用端自己来实现的,包括:连接的统一管理,如何保活等;如何保活之前我们了解一下为什么需要保活?主要原因是网络不是100%可靠的,我们创建好的连接可能由于网络原因导致连接已经不可用了,如果连接一直有消息往来,那么系统马上可以感知到连接断开;但是我们系统可能长时间没有消息来往,导致系统不能及时感知到连接不可用,也就是不能及时处理重连或者释放连接;常见的保活策略使用心跳机制由应用层来实现,还有网络层提供的TCP Keepalive保活探测机制;
1.3 TCP Keepalive机制
TCP Keepalive是操作系统实现的功能,并不是TCP协议的一部分,需要在操作系统下进行相关配置,开启此功能后,如果连接在一段时间内没有数据往来,TCP将发送Keepalive探针来确认连接的可用性,Keepalive几个内核参数配置:
tcp_keepalive_time:连接多长时间没有数据往来发送探针请求,默认为7200s(2h);
tcp_keepalive_probes:探测失败重试的次数默认为10次;
tcp_keepalive_intvl:重试的间隔时间默认75s;
以上参数可以修改到/etc/sysctl.conf文件中;是否使用Keepalive用来保活就够了,其实还不够,Keepalive只是在网络层就行保活,如果网络本身没有问题,是统由于其他原因已经不可用了,这时候Keepalive并不能发现;所以往往还需要结合心跳机制来一起使用;
1.4 心跳机制
何为心跳机制,简单来讲就是客户端启动一个定时器用来定时发送请求,服务端接到请求进行响应,如果多次没有接受到响应,那么客户端认为连接已经断开,可以断开半打开的连接或者进行重连处理;下面以Dubbo为例来看看是如何具体实施的;
Dubbo2.6.X
在HeaderExchangeClient中启动了定时器ScheduledThreadPoolExecutor来定期执行心跳请求:
1 | |
在实例化HeaderExchangeClient时启动心跳定时器:
1 | |
}
1 | |
}
1 | |
因为Dubbo双端都会发送心跳请求,所以可以发现有两个时间点分别是:lastReadlastWrite;当然时间和最后读取,最后写的时间间隔大于heartbeat就会发送心跳请求;如果多次心跳未返回结果,也就是最后读取消息时间大于heartbeatTimeout会判定当前是Client还是Server,如果是Client会发起reconnect,Server会关闭连接,这样的考虑是合理的,客户端调用是强依赖可用连接的,而服务端可以等待客户端重新建立连接;以上只是介绍的Client,同样Server端也有相同的心跳处理,在可以查看HeaderExchangeServer;
Dubbo2.7.0
Dubbo2.7.0的心跳机制在2.6.X的基础上得到了加强,同样在HeaderExchangeClient中使用HashedWheelTimer开启心跳检测,这是Netty提供的一个时间轮定时器,在任务非常多,并且任务执行时间很短的情况下,HashedWheelTimer比Schedule性能更好,特别适合心跳检测;
1 | |
HeartbeatTimerTask:用来定时发送心跳请求,心跳间隔时间默认为60秒;这里重新计算了时间,其实就是在原来的基础上除以3,其实就是缩短了检测间隔时间,增大了及时发现死链的概率;分别看一下两个任务:
1 | |
}
同上检测最后读写时间和heartbeat的大小,注:普通请求和心跳请求都会更新读写时间;
Long now = now();
1 | |
同样的在超时的情况下,Client重连,Server关闭连接;同样Server端也有相同的心跳处理,在可以查看HeaderExchangeServer;
Dubbo2.7.1-X
在Dubbo2.7.1之后,借助了Netty提供的IdleStateHandler来实现心跳机制服务:
public IdleStateHandler(
long readerIdleTime, long writerIdleTime, long allIdleTime,
TimeUnit unit) {
this(false, readerIdleTime, writerIdleTime, allIdleTime, unit);
}
1 | |
protected void initChannel(Channel ch) throws Exception {
final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
int heartbeatInterval = UrlUtils.getHeartbeat(getUrl());
ch.pipeline().addLast(“client-idle-handler”, new IdleStateHandler(heartbeatInterval, 0, 0, MILLISECONDS))
.addLast(“handler”, nettyClientHandler);
1 | |
Client端在NettyClient中添加了IdleStateHandler,指定了读写超时时间默认为60秒;60秒内没有读写事件发生,会触发IdleStateEvent事件在NettyClientHandler处理:
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
try {
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
super.userEventTriggered(ctx, evt);
}
1 | |
protected void initChannel(NioSocketChannel ch) throws Exception {
int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
ch.pipeline().addLast(“server-idle-handler”, new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
.addLast(“handler”, nettyServerHandler);
1 | |
Server端指定的超时时间默认为60*3秒,在NettyServerHandler中处理userEventTriggered
try {
}
}
1 | |
本文标题: Dubbo分析之心跳设计
发布时间: 2019年01月19日 00:00
最后更新: 2026年09月16日 05:40
原始链接: https://haoxiang.eu.org/e149e51d/
版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0许可协议,转载请注明出处!

