心跳协议的设计

11 September 2015

分布式系统,或者说,一方依赖远程的另一方提供服务的系统中,最好有某种机制知道服务提供方是否还能正常提供服务。以便及时改变处理策略以及通知用户。这种机制我们称为心跳机制。因为在分布式系统中没有全局瞬时状态【1】,不存在立刻判断对方故障的方法。所以心跳机制是有延时的。

心跳机制的核心作用:证明目标还有心跳,换句话说,目标是否还能提供我想要的服务。我们在使用 TCP socket 的时候,能不能认为只要连接不断开我就能获取想要的服务呢?不能。如果有硬件或者软件故障导致机器重启,或者进程重启,可能没有机会断开连接。或者发生网络故障,TCP 底层会重试,应用层也不能较快知道连接问题。然后进一步想到 TCP 的 keepalive 机制,是否满足要求?从心跳机制的核心作用出发考虑,我们可以知道 TCP 连接的 keepalive 机制也是不能满足要求的。原因在于 keepalive 只能保证连接没有问题,当进程出现死锁或者阻塞的情况,虽然连接没有问题,但是已经不能正常提供服务了。

进一步考虑,心跳包的发送要在工作线程发送,不应该使用单独的线程来发送。道理和 keepalive 机制不满足的原因类似,心跳要证明的是能够提供服务,证明工作线程没有异常,使用单独的“心跳线程”不能到达这个效果。还有一点是,心跳包的发送和业务消息的发送使用同一个连接。原因类似。

下面继续讨论心跳失效的校验机制。

心跳协议的基本形式是,如果进程 C 依赖 S,那么 S 应该按固定的周期向 C 发送心跳,而 C 按固定的周期检查心跳。这看起来有点像看门狗的设计。在这个形式中,有三个量值需要仔细考虑:心跳包的发送间隔时间 Ts,心跳包接收检查的间隔 Tc,心跳失效的超时时间 timeout。通常发一个心跳就对应接收一个,所以 Ts 应当与 Tc 相等。而它们值的大小,比如说 1 秒或者 10 秒,关系到精度问题。这个问题需要根据实际情况进行折中。精度低(间隔大),就不能较快知道对方情况,反应也就慢。精度高,一方面会增大网络负担,另一方面考虑到网络延迟,较大可能会发生误判的情况。正是网络传输存在延迟的情况,timeout 的值不能设得太小。现在设连续发送 3 个心跳包 P1、P2、P3,P1 和 P3 没有时延,而 P2 时延非常大。那么心跳检查端在收到 P1 后,需要 2Ts 后收到 P3,此时确认错过了心跳包 P2。所以把 timeout 设为 2Ts 是比较合适的。

其他的考虑。

前面对检查时间的设计,隐含了一个假设,那就是发送和接收端使用的系统时间应该是一致的。所以两个系统需要 NTP 进行时间同步。

让心跳包额外携带一些有用的信息。比如在 S-C 结构中,如果有多个 S 提供服务,通过心跳包中的服务器负载信息,客户端可以进行收集统计,从而选择更好的服务器来提供服务。这实现了一种负载均衡方法。

可以考虑设计可变的心跳速率。适用的情况是网络延迟状况变化大、系统负载变化大、不同时段提供不同的精度。这需要一方判断变更心跳速率的时机,然后双方协同变更使用新的速率。

为心跳包设计顺序递增的序号。因为心跳包是按固定周期持续发送的,这些序号就形成了一系列的游标。可以在一些特殊用途上发挥作用。比如云风设计的心跳服务器,还有《KylinOS 可靠心跳协议研究》这篇论文中提到的“心跳信息与普通信息以相同的渠道和序号进行发送”。

【1】分布式系统的各个子系统通过网络连接交互,而网络通信是有时延的,也会有不可预料的通信故障。所以说没有全局瞬时状态。

参考:

《Linux多线程服务端编程》9.3 节 分布式系统中心跳协议的设计

扩展阅读:

《KylinOS 可靠心跳协议研究》

《TCP Keepalive HOWTO》

杰良-2015-09-11

上一篇: 《为何爱会伤人》—— 一种修炼,两人都要修炼

下一篇: 广州-英西峰林骑行