分布式心跳协议的设计
应用层心跳的必要性
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 连接畅通,那其意义就大为缩水了
参考资料
- 陈硕. Linux 多线程服务端编程[J]. 2009.
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 小谷的编程随笔空间!
评论