应用层心跳的必要性

TCP 连接中,任何一方意外退出的时候,另外一方都能及时得到连接断开的通知,这是因为 操作系统会关闭进程使用中的 TCP Socket,然后向对方发送 FIN

尽管如此,但应用层的心跳仍然必不可少,原因:

  • 操作系统崩溃导致机器重启,没有机会发送 FIN 分节
  • 硬件故障导致机器重启,没有机会发送 FIN 分节
  • 并发连接数很高时,操作系统或进程如果重启,可能没有机会断开全部连接。
    • 换句话说,FIN 分节可能出现丢包,但这时没有机会重试。
  • 网络故障,连接双方得知这一情况的唯一方案是检测心跳超时。

TCP keeplive 不能替代应用层心跳

类似 gRPC 基于 HTTP/2,同样提供了 keeplive 机制。但这也不能替代应用层心跳。心跳除了说明应用程序还活着(进程还在,网络通畅),更重要的是表明应用程序还能正常工作。

TCP keepalive 由操作系统负责探查,即便进程死锁或阻塞,操作系统也会如常收发 TCP keepalive 消息。对方无法得知这一异常。

基本形式

如果进程 C 依赖 S,那么 S 应该按固定周期向 C 发送心跳,而 C 按固定的周期检查心跳。通常是服务器向客户端发送心跳

心跳的检查很简单,如果 Receiver 最后一次收到心跳消息的时间与当前时间之差超过某个 timeout 值,那么就判断对方心跳失效。

分布式系统没有全局瞬时状态,不存在立刻判断对方故障的方法,这是分布式系统的本质困难。

心跳协议的内在矛盾:高置信度与低反应时间不可兼得

注意事项

  • 两台机器的时间应该都通过 NTP 协议与时间服务器同步,否则几秒的时钟差可能造成误判心跳失效
  • 考虑到闰秒的影响,小于 1 秒是无意义的,因为闰秒会让两台机器的相对时差发生跳变,可能造成误报警
  • 要在工作线程发送,不要单独起一个「心跳线程」
    • 防止工作线程死锁或阻塞时还在继续发心跳
  • 与业务消息用同一个连接,不要单独用「心跳连接」
    • 如果它验证的不是收发业务数据的 TCP 连接畅通,那其意义就大为缩水了

参考资料

  1. 陈硕. Linux 多线程服务端编程[J]. 2009.