前言

谈到RPC肯定绕不开TCP通信,而主流的RPC框架都依赖于Netty等通信框架,这时候我们还要考虑是使用长连接还是短连接:

  • 短连接:每次通信结束后关闭连接,下次通信需要重新创建连接;优点就是无需管理连接,无需保活连接;

  • 长连接:每次通信结束不关闭连接,连接可以复用,保证了性能;缺点就是连接需要统一管理,并且需要保活;

主流的RPC框架都会追求性能选择使用长连接,所以如何保活连接就是一个重要的话题,也是本文的主题,下面会重点介绍一些保活策略;

为什么需要保活

上面介绍的长连接、短连接并不是TCP提供的功能,所以长连接是需要应用端自己来实现的,包括:连接的统一管理,如何保活等;如何保活之前我们了解一下为什么需要保活?

主要原因是网络不是100%可靠的,我们创建好的连接可能由于网络原因导致连接已经不可用了,如果连接一直有消息往来,那么系统马上可以感知到连接断开;

但是我们系统可能长时间没有消息来往,导致系统不能及时感知到连接不可用,也就是不能及时处理重连或者释放连接;常见的保活策略使用心跳机制由应用层来实现,还有网络层提供的TCP Keepalive保活探测机制;

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并不能发现;所以往往还需要结合心跳机制来一起使用;

心跳机制

何为心跳机制,简单来讲就是客户端启动一个定时器用来定时发送请求,服务端接到请求进行响应,如果多次没有接受到响应,那么客户端认为连接已经断开,可以断开半打开的连接或者进行重连处理;下面以Dubbo为例来看看是如何具体实施的;

Dubbo2.6.X

在HeaderExchangeClient中启动了定时器ScheduledThreadPoolExecutor来定期执行心跳请求:

ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("dubbo-remoting-client-heartbeat", true));

在实例化HeaderExchangeClient时启动心跳定时器:

private void startHeartbeatTimer() {stopHeartbeatTimer();        if (heartbeat > 0) {heartbeatTimer = scheduled.scheduleWithFixedDelay(new HeartBeatTask(new HeartBeatTask.ChannelProvider() {                        @Overridepublic Collection<Channel> getChannels() {                            return Collections.<Channel>singletonList(HeaderExchangeClient.this);}}, heartbeat, heartbeatTimeout),heartbeat, heartbeat, TimeUnit.MILLISECONDS);}
}

heartbeat默认为60秒,heartbeatTimeout默认为heartbeat*3,可以理解至少出现三次心跳请求还未收到回复才会任务连接已经断开;HeartBeatTask为执行心跳的任务:

public void run() {long now = System.currentTimeMillis();        for (Channel channel : channelProvider.getChannels()) {            if (channel.isClosed()) {                continue;}            Long lastRead = (Long) channel.getAttribute(HeaderExchangeHandler.KEY\_READ\_TIMESTAMP);            Long lastWrite = (Long) channel.getAttribute(HeaderExchangeHandler.KEY\_WRITE\_TIMESTAMP);            if ((lastRead != null && now - lastRead > heartbeat)|| (lastWrite != null && now - lastWrite > heartbeat)) {                // 发送心跳}            if (lastRead != null && now - lastRead > heartbeatTimeout) {                if (channel instanceof Client) {((Client) channel).reconnect();} else {channel.close();}}}
}

因为Dubbo双端都会发送心跳请求,所以可以发现有两个时间点分别是:lastRead和lastWrite;当然时间和最后读取,最后写的时间间隔大于heartbeat就会发送心跳请求;

如果多次心跳未返回结果,也就是最后读取消息时间大于heartbeatTimeout会判定当前是Client还是Server,如果是Client会发起reconnect,Server会关闭连接,这样的考虑是合理的,客户端调用是强依赖可用连接的,而服务端可以等待客户端重新建立连接;

以上只是介绍的Client,同样Server端也有相同的心跳处理,在可以查看HeaderExchangeServer;

Dubbo2.7.0

Dubbo2.7.0的心跳机制在2.6.X的基础上得到了加强,同样在HeaderExchangeClient中使用HashedWheelTimer开启心跳检测,这是Netty提供的一个时间轮定时器,在任务非常多,并且任务执行时间很短的情况下,HashedWheelTimer比Schedule性能更好,特别适合心跳检测;

HashedWheelTimer heartbeatTimer = new HashedWheelTimer(new NamedThreadFactory("dubbo-client-heartbeat", true), tickDuration,TimeUnit.MILLISECONDS, Constants.TICKS\_PER\_WHEEL);

分别启动了两个定时任务:startHeartBeatTask和startReconnectTask:

private void startHeartbeatTimer() {AbstractTimerTask.ChannelProvider cp = () -> Collections.singletonList(HeaderExchangeClient.this);        long heartbeatTick = calculateLeastDuration(heartbeat);        long heartbeatTimeoutTick = calculateLeastDuration(heartbeatTimeout);HeartbeatTimerTask heartBeatTimerTask = new HeartbeatTimerTask(cp, heartbeatTick, heartbeat);ReconnectTimerTask reconnectTimerTask = new ReconnectTimerTask(cp, heartbeatTimeoutTick, heartbeatTimeout);          // init task and start timer.heartbeatTimer.newTimeout(heartBeatTimerTask, heartbeatTick, TimeUnit.MILLISECONDS);heartbeatTimer.newTimeout(reconnectTimerTask, heartbeatTimeoutTick, TimeUnit.MILLISECONDS);
}

HeartbeatTimerTask:用来定时发送心跳请求,心跳间隔时间默认为60秒;这里重新计算了时间,其实就是在原来的基础上除以3,其实就是缩短了检测间隔时间,增大了及时发现死链的概率;分别看一下两个任务:

protected void doTask(Channel channel) {Long lastRead = lastRead(channel);Long lastWrite = lastWrite(channel);        if ((lastRead != null && now() - lastRead > heartbeat)|| (lastWrite != null && now() - lastWrite > heartbeat)) {Request req = new Request();req.setVersion(Version.getProtocolVersion());req.setTwoWay(true);req.setEvent(Request.HEARTBEAT_EVENT);channel.send(req);}
}

同上检测最后读写时间和heartbeat的大小,注:普通请求和心跳请求都会更新读写时间;Netty 在 Dubbo 中是如何应用的?这篇推荐大家看一下。

protected void doTask(Channel channel) {Long lastRead = lastRead(channel);Long now = now();        if (lastRead != null && now - lastRead > heartbeatTimeout) {            if (channel instanceof Client) {((Client) channel).reconnect();} else {channel.close();}}
}

同样的在超时的情况下,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);
}
  • readerIdleTime:读超时时间;

  • writerIdleTime:写超时时间;

  • allIdleTime:所有类型的超时时间;

根据设置的超时时间,循环检查读写事件多久没有发生了,在pipeline中加入IdleSateHandler之后,可以在此pipeline的任意Handler的userEventTriggered方法之中检测IdleStateEvent事件;下面看看具体Client和Server端添加的IdleStateHandler:

Client端

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);
}

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);Request req = new Request();req.setVersion(Version.getProtocolVersion());req.setTwoWay(true);req.setEvent(Request.HEARTBEAT_EVENT);channel.send(req);} finally {NettyChannel.removeChannelIfDisconnected(ctx.channel());}} else {            super.userEventTriggered(ctx, evt);}
}

可以发现接收到IdleStateEvent事件发送了心跳请求;至于Client端如何处理重连,同样在HeaderExchangeClient中使用HashedWheelTimer定时器启动了两个任务:心跳任务和重连任务,感觉这里已经不需要心跳任务了,至于重连任务其实也可以放到userEventTriggered中处理;

Server端

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);
}

Server端指定的超时时间默认为60*3秒,在NettyServerHandler中处理userEventTriggered

public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {        if (evt instanceof IdleStateEvent) {NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);            try {channel.close();} finally {NettyChannel.removeChannelIfDisconnected(ctx.channel());}}        super.userEventTriggered(ctx, evt);
}

Server端在指定的超时时间内没有发生读写,会直接关闭连接;相比之前现在只有Client发送心跳,单向发送心跳;

同样的在HeaderExchangeServer中并没有启动多个认为,仅仅启动了一个CloseTimerTask,用来检测超时时间关闭连接;感觉这个任务是不是也可以不需要了,IdleStateHandler已经实现了此功能;

综上:在使用IdleStateHandler的情况下来同时在HeaderExchangeClient启动心跳+重连机制,HeaderExchangeServer启动了关闭连接机制;主要是因为IdleStateHandler是Netty框架特有了,而Dubbo是支持多种底层通讯框架的包括Mina,Grizzy等,应该是为了兼容此类框架存在的;

总结

本文首先介绍了RPC中引入的长连接方式,继而引出长连接的保活机制,为什么需要保活?然后分别介绍了网络层保活机制TCP Keepalive机制,应用层心跳机制;最后已Dubbo为例看各个版本中对心跳机制的进化。

作者:ksfzhaohui317

https://segmentfault.com/a/1190000022591346

关注微信公众号:互联网架构师,在后台回复:2T,可以获取我整理的教程,都是干货。

猜你喜欢

1、GitHub 标星 3.2w!史上最全技术人员面试手册!FackBoo发起和总结

2、如何才能成为优秀的架构师?

3、从零开始搭建创业公司后台技术栈

4、程序员一般可以从什么平台接私活?

5、37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...

6、滴滴业务中台构建实践,首次曝光

7、不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事

8、15张图看懂瞎忙和高效的区别

9、2T架构师学习资料干货分享

Dubbo 的心跳设计,值得学习!相关推荐

  1. 设计灵感|高饱和渐变创意海报设计作品,值得学习

    所谓渐变色是指某个物体的颜色从明到暗,或由深转浅,或是从一个色彩缓慢过渡到另一个色彩,充满变幻无穷的神秘浪漫气息的颜色. 色彩可以给我们不同的感受和情绪,而渐变色则可以给我们更多的想象空间.纯粹的渐变 ...

  2. 交互设计基本功!5个值得学习的APP交互方式

    交互设计基本功!5个值得学习的APP交互方式 一.下拉输入 代表应用:Flickr.Opera Coast.any.do.Timi记账 即通过下拉的动作来呼出输入操作,比如搜索.添加项目等,比如fli ...

  3. 【Spring Cloud Alibaba】(五)Dubbo启动报错?一直重连报错?你值得学习的是排查问题的方法

    系列目录 [Spring Cloud Alibaba](一)微服务介绍 及 Nacos注册中心实战 [Spring Cloud Alibaba](二)微服务调用组件Feign原理+实战 [Spring ...

  4. 2021年,Java开发者值得学习的13项技能

    本文分享自百度开发者中心2021年,Java开发者值得学习的13项技能 作者 | Olivia Cuthbert 译者 | 王强 策划 | 刘燕 如果你想在这个竞争激烈的世界里,成为一名熟练开发 Ja ...

  5. 哪些设计模式最值得学习

    最近又在首页看到几篇设计模式相关的学习随笔.回想起来,这几年在园子里发布的有关设计模式的随笔都有一个共同的特点.那就是Factory和Singleton居多,如果是系列的,也往往是从这两个模式开始的. ...

  6. Qt值得学习吗?详解Qt的几种开发方式

    qt值得学习吗? 嵌入式要学的东西真的很多,我们可能会说不写界面的话就不用学qt了?我不赞同. Qt的实现主要是采用p-impl手法,实现接口与实现分离,它有很好的消息循环机制,有的对象与线程的相关性 ...

  7. 开源java项目_请问有哪些开源java项目值得学习的?学习完了容易找工作的?

    Gitee 小编精选了六个 GItee 上的 GVP(Gitee Most Valuable Project) Java 开源项目,种类丰富,覆盖各行各业,希望可以帮到你:) 更多值得学习的优质开源项 ...

  8. 值得学习的C/C++开源框架(转)

    值得学习的C语言开源项目 - 1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的 ...

  9. Rust 升级成微软第一梯队语言;“熊孩子”乱敲键盘攻破 Linux 桌面;500 个值得学习的 AI 开源项目| 开发者周刊...

    整理 | 梦依丹 出品 | CSDN(ID:CSDNnews) CSDN开发者周刊:只为传递"有趣/有用"的开发者内容! 本周热门项目 0.Rust 升级成为微软一级项目 2015 ...

  10. 【周刊】“熊孩子”乱敲键盘攻破 Linux 桌面;500 个值得学习的 AI 开源项目;Rust 升级成为微软一级项目...

    整理 | 梦依丹 出品 | CSDN(ID:CSDNnews) CSDN开发者周刊:只为传递"有趣/有用"的开发者内容! 本周热门项目 0.Rust 升级成为微软一级项目 2015 ...

最新文章

  1. /dev/urandom
  2. 前嗅ForeSpider教程:采集图片/视频/资源文件的链接地址
  3. Rainbow的相关资料
  4. oracle中create table with as和insert into with as语句
  5. 【转】使用genstring和NSLocalizedString实现App文本的本地化
  6. Linux系统下按了Ctrl+s锁定屏幕后怎么办?
  7. Mysql 的表级锁和行级锁
  8. 怎么在win7链接无线网络连接服务器,Win7系统网络连接一直显示正在获取网络地址但是连不上网解决方法...
  9. 腾讯想拿到Big Data资源,8h删抓紧时间!!
  10. MD5和AES加密的处理
  11. Postman下载与安装操作步骤【超详细】
  12. 能耗分项计量监测系统在某大型公建中的应用
  13. json校验失败的原因
  14. 开源直播课丨大数据集成框架ChunJun类加载器隔离方案探索及实践
  15. 第十节 Java工具包-Collections 流--lambada表达式
  16. crm系统客户池功能流程泳道图
  17. 你看到的是乱,我看到的是月亮,如是而已。
  18. 纯净版win10系统下载 2020-12-17
  19. 极智Paper | 单级特征检测网络 YOLOF
  20. uniapp h5 web-view不显示公众号文章

热门文章

  1. 编程总结一 查找整数
  2. 2019梅江(天津)年货展销会启幕
  3. 前后端分离项目,标准json协议格式参考
  4. 客户端向hdfs读写数据流程
  5. Laravel 2017 年度调查报告
  6. 《拥抱机器人时代——Servo杂志中文精华合集》——3.6 物联网有多么重要
  7. mysql 大小写问题—20161102
  8. corosycnpacemaker的高可用web集群
  9. [SHELL进阶] (转)最牛B的 Linux Shell 命令 (三)
  10. nmap 命令的使用