4、心跳检测机制:TCP Keep-Alive、应用层心跳、超时重试策略
说到交易系统的容错,心跳检测是我最常被问到的话题之一。
很多人觉得心跳嘛,不就是定时发个包确认对方活着?其实没那么简单。我见过不少系统,心跳机制写得挺漂亮,结果一上线就出问题——要么误判断连导致频繁切换,要么心跳本身成了性能瓶颈。
今天咱们就把这块掰开揉碎了讲清楚。
4.1 为什么需要心跳检测?
交易系统里,网络连接是不可靠的。TCP 连接看起来还连着,实际上对端可能已经挂了。这种现象叫「半开连接」(Half-Open Connection)。
举个例子:你下单的网关进程突然崩溃了,但操作系统还没来得及发 FIN 包。你的交易客户端还傻傻地以为连接正常,继续发单——结果单子全丢了。
心跳检测就是为了解决这个问题。它定期确认对端是否存活,一旦发现异常,立即触发重连或切换。
4.2 TCP Keep-Alive:系统自带的心跳
TCP 协议本身提供了一个 Keep-Alive 机制。说白了,就是操作系统帮你定期发一个空数据包,看看对方有没有回应。
我个人的习惯是:不依赖 TCP Keep-Alive 做核心检测。为什么?
- 检测周期太长:默认情况下,Linux 要等 2 小时才发第一个探测包。就算你调参数,也很难低于 10 秒。
- 粒度太粗:它只能检测 TCP 层是否通,应用层卡死了它不知道。
- 跨平台不一致:Windows、Linux、macOS 的参数设置完全不同。
不过,TCP Keep-Alive 也不是完全没用。我一般把它当作最后一道防线——应用层心跳失效时,它能兜底清理死连接。
sysctl -w net.ipv4.tcp_keepalive_time=30 (30秒后开始探测)sysctl -w net.ipv4.tcp_keepalive_intvl=5 (每次间隔5秒)sysctl -w net.ipv4.tcp_keepalive_probes=3 (连续3次无响应则断开)
4.3 应用层心跳:真正的核心
交易系统里,我们几乎都用应用层心跳。说白了,就是在业务协议里加一个专门的 Ping/Pong 消息。
我曾经在一个期货高频交易项目里,就因为心跳设计不合理,导致频繁误切。后来我总结了一套经验:
4.3.1 心跳协议设计
最简单的做法:客户端定时发 Ping,服务端回复 Pong。但这里有几个坑:
- Ping 和 Pong 要带序列号:防止历史心跳包干扰判断
- 服务端也要主动发心跳:防止客户端单方面认为连接正常
- 心跳包要轻量:别带业务数据,就一个消息头加时间戳
// 一个典型的心跳消息结构
struct HeartbeatMessage {
uint8_t type; // 0x01 = Ping, 0x02 = Pong
uint32_t sequence; // 递增序列号
uint64_t timestamp; // 发送时间戳(纳秒)
uint8_t reserved[7]; // 对齐填充
} __attribute__((packed));
4.3.2 心跳间隔与超时
这是最容易出问题的地方。我见过有人把心跳间隔设成 1 秒,超时设成 3 秒——结果网络稍微抖动一下,就开始切主备了。
我的经验公式:
| 场景 | 心跳间隔 | 超时次数 | 总超时时间 |
|---|---|---|---|
| 同机房交易 | 1 秒 | 3 次 | 3 秒 |
| 同城跨机房 | 2 秒 | 3 次 | 6 秒 |
| 异地灾备 | 5 秒 | 2 次 | 10 秒 |
4.4 超时重试策略
心跳检测发现连接断了,接下来怎么办?直接重连?别急,这里也有讲究。
4.4.1 指数退避(Exponential Backoff)
我最常用的策略。每次重试间隔翻倍,防止雪崩效应。
import time
import random
def connect_with_backoff(max_retries=5, base_delay=0.1):
"""
带指数退避的重连策略
"""
for attempt in range(max_retries):
try:
# 尝试建立连接
connection = create_connection()
return connection
except ConnectionError as e:
# 计算退避时间:base * 2^attempt + 随机抖动
delay = base_delay * (2 ** attempt) + random.uniform(0, 0.1)
print(f"第 {attempt+1} 次重连失败,等待 {delay:.2f} 秒...")
time.sleep(delay)
raise Exception("重连失败,已达最大重试次数")
4.4.2 快速重试 + 慢速恢复
交易系统里,我更喜欢这种策略:
- 前 3 次:间隔 100ms,快速尝试恢复
- 3-10 次:间隔 1 秒,中等频率
- 10 次以后:间隔 10 秒,降低频率防止打满 CPU
这样做的好处是:如果是瞬断,能快速恢复;如果是持续故障,不会浪费资源。
4.4.3 熔断机制
连续重试失败超过一定次数,我建议直接熔断。什么意思?就是停止重试,上报告警,等待人工介入。
- 连续 15 次重试失败 → 熔断
- 熔断持续时间:30 秒
- 熔断结束后:尝试半开探测(发一次心跳,成功则恢复,失败则继续熔断)
4.5 整体架构图
下面这张图展示了心跳检测与重试的完整流程:
4.6 实战中的几个关键点
最后,分享几个我在实战中踩过的坑:
- 心跳不要和业务消息混在一起:我曾经把心跳和行情数据放在同一个线程处理,结果行情拥堵时心跳也发不出去,导致误判。后来单独开一个线程处理心跳。
- 记录心跳日志:每次心跳超时或重连,都要打日志。排查问题时,这些日志就是你的救命稻草。
- 测试网络抖动场景:用 tc 命令模拟丢包、延迟,看看你的心跳机制能不能扛住。我一般会测 1%、5%、10% 三种丢包率。
- 考虑时钟同步:如果心跳带时间戳,确保客户端和服务端的时钟是同步的。NTP 服务一定要配好。
嗯,心跳检测这块内容就这些。说白了,就是「定期确认对方活着,死了就赶紧重连」。但细节决定成败——间隔设多少、超时几次、重试策略怎么选,每个参数都直接影响系统的稳定性。
我见过太多系统,因为心跳参数没调好,导致频繁切换、交易中断。希望今天的分享能帮你避开这些坑。