心跳包

什么是心跳包(心跳机制)

先看一下wiki上的说法:

心跳包(英语:Heartbeat)在计算机科学中指一种周期性的信号,通过硬件或软件的形式来检测行为的正常与否,或者与计算机系统是否一致。[1] 通常,机器间会每隔几秒钟发送一次心跳包。 如果接收终端没有在指定时间内(通常是几个心跳包发送的时间间隔内)接收到心跳包,发送终端将会被判定发送失败。

简而言之心跳机制是用于检测对端存活的一种常用方式。

有点类似icu里面的心跳检测机(服务端),你的心脏(客户端)跳一下,他就更新一下状态,认为你还活着,你要太长时间没跳,他就认为你已经不行了,然后发出bi 的警告。

在网络中,心跳的作用是,在一种需要对端保持连接的状态中,并且存在无法通过上一次的请求判断当前的状态(虽然心跳也不能保证下一次发送成功,但是现实是,我上一次请求是12h前发的,所以你现在还在不在),心跳包可以单独检测对端的存活状态,从而防止发送无用的数据包,另外在分布式系统中,可以避免将数据发送到不可用的节点上(这是比较麻烦的,我成功地把包发到一个不可用的节点上,它给我反错误了,我怎么办,发给其它节点吗,会造成双花吗?)

当然心跳机制不会完美地解决上面的这些问题(毕竟我也不能保证这一秒你的心还在跳,下一秒你就一定还活着),在高可用的系统中还是需要设计另外的机制来防止双花。

另外值得说的一点是:我们不能在网络中发送过多的心跳包,因为在很多时候,网络也是一直有限的资源(心跳虽好,可不要贪杯o),当然也有设计感觉网络情况动态调整的心跳机制,不过那就涉及一些网络底层的东西了。

常见的心跳包

keepalive

说到常见的心跳包,就不得不说tcp keepalive机制了

依然是wiki:

传输控制协议(TCP)存活包为可选特性,且默认关闭。[1]存活包内没有数据。在以太网网络中,存活包的大小为最小长度的几帧(64字节[2])。协议中[3],还有三个与存活包相关的参数:

存活时长(英语:Keepalive time)即空闲时,两次传输存活包的持续时间。TCP存活包时长可手动配置,默认不少于2个小时。

存活间隔(英语:Keepalive interval)即未收到上个存活包时,两次连续传输存活包的时间间隔。

存活重试次数(英语:Keepalive retry)即在判断远程主机不可用前的发送存活包次数。当两个主机透过TCP/IP协议相连时,TCP存活包可用于判断连接是否可用,并按需中断。

多数支持TCP协议的主机也同时支持TCP存活包。每个主机按一定周期向其他主机发送TCP包来请求回应。若发送主机未收到特定主机的回应(ACK),则将从发送主机一侧中断连接。 若其他主机在连接关闭后发送TCP存活包,关闭连接的一方将发送RST包来表明旧连接已不可用。其他主机将关闭它一侧的连接以新建连接。

空闲的TCP连接通常会每隔45秒或60秒发送一次存活包。在未连续收到三次ACK包时,连接将中断。此行为因主机而异,如默认情况下的Windows主机将在7200000ms(2小时)后发送首个存活包,随后再以1000ms的间隔发送5个存活包。若任意存活包未收到回应,连接将被中断。

keepalive作为最基础的心跳机制,其设计已经融入tcp协议中了。

  • wireshark keepalive捉包分析

TLS的心跳机制与心脏出血漏洞

TLS心跳原理rfc 6520这里就之间放rfc的原文了,感兴趣的可以去读读看。

简单来讲TLS心跳拓展主要解决的是在tls链路中,判断对方存活需要进行一次tls协商(这是比较费时),这个心跳拓展的主要目的是通过一个简单的心跳过程来保留tls链路的存活,在之前是使用tcp的keepalive来做的,但tcp的keepalive只能保证tcp链路的可用性。

看完这篇rfc,有两个比较有意思的点

对于每个心跳包,我们需要给他一个payload,而服务端返回的时候需要原封不动的返回这个payload。这么做我猜测是外来解决网络超时的问题,防止我受到之前的包

不需要时时刻刻的发送心跳包,感觉rfc的定义,我们只需要在网络空闲的时候发送心跳包,而在链路中有请求的情况下则不需要发送请求包。

IM系统中的心跳机制

IM系统(通讯系统)中的心跳机制主要是获取用户在线状态,以及向用户推送数据用

与前面两种心跳类似,不过IM系统需要面对一个麻烦的东西 -- NAT(当然TLS的心跳也有考虑NAT的因素)。对于IM系统,本质是是一个C/S架构的系统,而大部分的C都是没有独立IP的,与server通讯,全靠NAT分配的临时IP与端口,而NAT的反配权又不是C端掌控的,实际上是运营商在控制NAT的分配与释放。同时IM系统中一般C的数量会是S数量的几千-几万倍,维护心跳状态将会耗费大量的资源,不过值得庆幸的是,IM实际上是一种弱可用的系统,服务端不需要对客户端的心跳做出反应,也不需要向客户端发送心跳包,有点类似于UDP,客服端发出去就不管了。当然IM的心跳机制还是颇为复杂的,而他的复杂也不是我想要了解的信息,所以这里只给出一片作者认为还可以的博客

应用层上的心跳包

上面谈到的心跳机制基本上都是网络层面的心跳机制,更多的是确认一个链路是否还可用。当然我们可以把这个链路再抽象一下,比如在一个对等网络中,你连接了某个数据库的资源,我连接了另一个数据库的资源,而我们需要保证相互之间 到数据库的链路是连通的。那么这就不是简单的tcp keepalive这种模式能解决的问题了。我们没法在网络传输模块完成整个心跳过程,网络层甚至不知道数据库是什么,所以这个请求必须上抛到应用层,而应用层在根据自身情况,去找数据库拿状态(这里有个情况,为什么不让数据库也跳起来,主要是浪费资源,心跳是检测活性,如果没有client,实际上也用不到数据库的活性,对于心跳包中复杂的请求,应该被动的等待需要的时候再去操作,而不是在主动地推送自己的状态)。

当然这还会有一些问题我们包心跳的逻辑全部放在应用层,是不是对心跳是不很友好,这会导致应用层的逻辑与网络层的逻辑耦合在一起,本来应该网络层做的心跳,可所有的逻辑都在应用层,网络层就像完全没有心跳这回事。

心跳方案的设计

说这么多,最后我们还是回到现实,最近我的一开发任务,为我们的分布式系统添加一个心跳检测机制。

简单描述一下我们的系统:

一个分布式的系统,每个peer会连接一个或多个资源,一个资源会被多个peer连接,当peer受到请求后会随即的把请求发送到他知道能处理这个请求的peer中。peer之间使用json_rpc通行(用rpc协议是因为我们这个过程实际上就是一个远程调用)。

比如我是a,我知道b,c,d能处理 x的请求,我会随即的把请求发到b,c,d某个peer中(或者发送到第一个peer)。

现在遇到的一个问题是,如果b,c,d中间有人挂机了,a是不知道的,而a还会随即的把请求发到一个节点里面。

我现在的设计方案是,在rpc模块实现心跳的逻辑,包括自动发送心跳包,判断返回结果是否正常,对于多次心跳异常的节点进行处理,(对方节点死了也要周期性的那,用于复活),也就是心跳的主要逻辑在rpc中实现(网络模块),它用于控制心跳的评论等等,然后在rpc中抽象出两个接口,HeartbeatHandler,HeartbeatClient

  • HeartbeatHandler 接口表示心跳服务端处理的接口

  • HeartbeatClient 接口表示心跳客户端的接口: 客户端接口只需要提供两个方法,一个属如果构造心跳请求的接口,一个属如何处理心跳结果的接口

在server 启动时,将一个HeartbeatHandler的接口注册到server中,在client实例化的时候设置HeartbeatClient 并开启心跳,而心跳的流程控制还在网络模块中。

在我的这个业务中,HeartbeatHandler接口在收到心跳请求会,回去找当前节点连接的资源获取状态,让后返回资源是否可用的结果给clinet

client根据结果来刷新自己的路由表,确保下次请求发送到一个状态最健康的节点上。

 

**WRAN 以上文章只是出自我的初步调研,若有疏漏,还请同好们多多指正**

posted @ 2024-06-01 14:43  bighu  阅读(296)  评论(0编辑  收藏  举报