调优集群故障切换时间
Aeron® Cluster 的 leader 故障切换时间完全可调 —— 而默认值很慢。开箱配置从一次 leader 丢失中恢复要 花 约 11 秒,其中几乎全耗在一个超时上。你可以把它压到 亚秒级,但这根旋钮是双刃的:拧得太紧, 一个只是短暂沉默的 健康 leader 就会被无缘无故投票下台。本页就讲怎么权衡这笔账。
字节级的选举内部机制 —— term、canvass、投票记录 —— 见 The Aeron Files。 本页是运维视角:该拧什么、能换来什么、又要冒什么险。它与 Leader 放置与优雅让位(新 leader 落在 哪里)互为搭档 —— 本页讲的是它 多快 能到位。
“故障切换时间”到底由什么构成
Section titled ““故障切换时间”到底由什么构成”故障切换 = 检测(察觉 leader 不在了)+ 选举(投出新 leader)+ 客户端重连(客户端把 ingress 重新指向新 leader)。从 kill 那一刻算起:
检测与选举 串行 进行,所以下限大致是 heartbeat.timeout + election.timeout + relink。
| 属性 | 默认值 | 作用 | 说明 |
|---|---|---|---|
aeron.cluster.leader.heartbeat.timeout | 10,000 ms | 检测 —— follower 在收到 leader 最后一次心跳后,等多久才宣告它已死 | 占开箱故障切换时间的约 90%;最大的一根杠杆 |
aeron.cluster.leader.heartbeat.interval | 200 ms | leader 多久发一次心跳 | 必须远小于 timeout(一个窗口内最好有 ≥3–5 拍) |
aeron.cluster.election.timeout | 1,000 ms | 检测之后的投票/选举阶段 | 在检测 之后 串行进行 |
aeron.cluster.election.status.interval | 100 ms | 选举轮次节奏 | |
aeron.cluster.session.timeout | 10,000 ms | 客户端 会话存活 | 与 leader 故障切换无关;决定集群多久驱逐一个沉默的客户端 |
aeron.cluster.startup.canvass.timeout | 60,000 ms | 冷启动 leader canvass | 与故障切换无关(仅启动时) |
默认情况下这些故障切换旋钮一个都没设,所以开箱的 10 s 检测主宰了一切。
为什么要调低 —— 好处
Section titled “为什么要调低 —— 好处”- 从真实 leader 丢失中恢复更快。 最直接的收益:我们的测试里从 ~11 s → ~0.85 s(约 13×)。真崩溃 之后,数据面被停顿、被反压的时间更短。
- 客户端可见的中断更短。 整个故障切换窗口内客户端 ingress 都被反压,窗口越短,吞吐缺口越小,恢复 时要排空的积压也越少。
- SLA 更紧、更可预期。 如果你必须承诺”在 N 秒内恢复”,调低超时是缩小 N 的唯一办法。
- 更早释放被占住的资源。 绑在死 leader 上的订阅、会话状态、流控窗口能更快清干净。
为什么不要 —— 风险
Section titled “为什么不要 —— 风险”误切换(首要风险)
Section titled “误切换(首要风险)”heartbeat.timeout 是一个 dead-man’s switch。只要一个 健康 的 leader 沉默超过窗口 —— 无论出于
什么 原因 —— follower 就会宣告它已死并跑一次 真实的选举:一次货真价实的 ~0.85–1.5 s 停顿外加
客户端反压,纯属自找,其实压根没出故障。窗口越紧,它兜住的良性停顿就越多。
会在健康 leader 上触发过紧窗口的停顿来源:
| 停顿来源 | 典型时长 | 缓解手段 |
|---|---|---|
| GC 暂停 | ZGC 亚毫秒;G1/Parallel 或大堆 → 100 ms–数秒 | 低暂停 GC(ZGC) |
| CPU 调度 / 抢占 | 若共享核则 ms–数百 ms | isolcpus + nohz_full 绑核 |
| Archive fsync / 磁盘卡顿 | 网络存储上 10–数百 ms | archive 放在快速本地存储 / tmpfs |
| 网络抖动 / 心跳丢包 | µs–ms | 同 AZ 放置组(低 RTT、无丢包) |
| VM steal / 吵闹邻居 | ms–数百 ms | 裸金属(无 hypervisor steal) |
| Safepoint / JIT / THP 整理 | ms–数百 ms | (残余尾部风险) |
其他失败模式
Section titled “其他失败模式”- interval 与 timeout 之比。
heartbeat.interval必须远小于heartbeat.timeout(一个窗口内争取 ≥3–5 拍)。只调低 timeout 却不调 interval,单个 延迟或丢失的包就会触发一次误切换。这是最常见的 误配。 - 选举反复。
election.timeout太短时,一次本该多花一点时间的正当选举(canvass、日志追赶)可能自己 就超时重启 → 选举 来回打转,反而让有效故障切换 更长 甚至不收敛 —— 与目标背道而驰。 - 负载下的 leader 抖动。 高负载下过紧的超时会让 leader 在节点间来回弹跳,每弹一次都逼着客户端重连 并换一个 term。一个反复抖动的集群,可用性可能 不如 一个慢但稳的故障切换。
- 短 soak 带来的虚假信心。 一次几分钟、零抖动的运行,并不能证明它能连续几天安然无恙 —— 生产环境会 叠加起伏的负载、长时运行才冒头的尾部 GC 事件、以及突发流量。信心需要一次 长时 soak(数十分钟到 数小时),而非一次短促的基准。
哪些东西 不会 因此更危险
Section titled “哪些东西 不会 因此更危险”调低超时,是把一种失败模式(恢复慢)换成另一种(自找的停顿)。它并 不会 引入正确性隐患:
- 不丢数据。 Aeron Cluster 的提交基于 quorum(Raft);已提交的数据无论时序如何都能挺过故障切换。见 Cluster 与 Raft 概览。
- 不会脑裂。 新 leader 需要 多数 票,所以一次误选举不可能产生两个 leader。误切换造成的损害纯粹 是一次 可用性抖动 —— 绝不会是数据损坏或双写者。
在一台裸金属机群(Granite Rapids,SNC-3)上实测,800K tps、128B 消息、MTU 8192,采用”负载下 kill
leader”的测试和一个 具备故障切换感知的客户端(实现了 EgressListener.onNewLeader 并调用
pollEgress())。基于 Aeron 1.48.11 验证;属性名与默认值通过反射运行中的 jar 确认。
开箱超时 → ~11 s,以及那个客户端陷阱
Section titled “开箱超时 → ~11 s,以及那个客户端陷阱”用开箱超时时,选举本身是健康的 —— 节点被 kill → 选出新 leader,0 NAK / 0 重传 / 0 错误,幸存 的 follower 什么都没丢(它在 kill 时已完整复制,故晋升无需重新同步;新 leader 记录了 2 个良性的追赶 NAK)。
实测真实故障切换 ≈ 11.0 s —— 三个独立信号相互吻合(新 leader 的 raft-log 发送位置、客户端字节恢复、
客户端 NEWLEADER 事件)。这正好是开箱的 10 s 心跳超时 + ~1 s 选举,与模型预测完全一致。
激进超时 → ~0.85 s,且不设下限
Section titled “激进超时 → ~0.85 s,且不设下限”设 heartbeat.timeout=250ms、heartbeat.interval=50ms、election.timeout=300ms、
election.status.interval=25ms —— Aeron 原样接受了每一个值;不存在强制下限。
| 试次 | 检测(ms) | 选举(ms) | 客户端重连(ms) | 总计(ms) | 误切换 |
|---|---|---|---|---|---|
| 1 | 337 | 637 | 764 | 809 | 0 |
| 2 | 338 | 695 | 808 | 833 | 0 |
| 3 | 339 | 805 | 902 | 947 | 0 |
| 中位数 | 338 | 695 | 808 | 833 | 0 |
- 稳定在 ~0.85 s —— 比开箱快约 13×,所有客户端都完整恢复。
- 没能进到 500 ms 以内,这是结构性的: 检测(~338 ms ≈ heartbeat.timeout)与 election.timeout (300 ms)串行 进行 → 在重连之前就已耗掉 ~640 ms 才选出 leader。要压到 500 ms,两项都得缩,而这 会侵蚀抖动余量。
- 误切换 = 0,横跨约 28,000 个稳态采样 ——
election_state=CLOSED,整个 kill 前窗口角色保持不变。 250 ms / 50 ms 在这台机器上不抖动(低暂停 GC、isolcpus、快速本地 archive、同 AZ)—— 这恰恰印证了 上面的告诫:它在 这里 安全。
取舍曲线与推荐
Section titled “取舍曲线与推荐”随着你把超时拧紧,两项代价的变化速率截然不同:
- 恢复速度的收益在 ~2 s 以下几乎持平 —— leader 故障本就 罕见,1.5 s 与 0.85 s 用户很少能感知到 差别。
- 误切换风险随拧紧而陡增 —— 这是一份 持续 的敞口(每一次 GC 暂停、safepoint、丢包延迟,每天每一 秒都在)。粗略地说,超时减半,兜住良性停顿而误触发选举的概率就翻倍。
从 2 s → 0.85 s 的边际收益很小,边际风险却很大。这就是 曲线的拐点。
| 档位 | 超时 | 故障切换 | 抖动余量(可容忍的 leader 停顿) | 何时用 |
|---|---|---|---|---|
| 保守 | 开箱(10 s / 1 s) | ~11 s | ~10 s(从不抖动) | 你从不 kill leader / 批处理系统 |
| 均衡(推荐) | hb 1000 / int 200 / elec 500 / stat 50 | ~1.5 s | ~1 s —— 覆盖几乎所有 GC/safepoint/调度尾部;对非理想环境也稳健 | 多数生产部署 |
| 激进(已测) | hb 250 / int 50 / elec 300 / stat 25 | ~0.85 s | ~250 ms —— 单次 G1 暂停 / THP 整理就可能超过 | 硬性亚秒 SLA、理想机器、且做过 soak 之后 |
推荐做法:
- 默认选均衡的 ~1–2 s 档(
heartbeat.timeout=1000ms、interval=200ms、election.timeout=500ms、election.status.interval=50ms)。它比开箱快约 7×,保留了激进档约 4× 的抖动余量,即便环境偏离理想也 仍然安全。 - 保持 interval ≪ timeout(一个窗口 ≥3–5 拍)。绝不要只调低 timeout。
- 只在有硬性 SLA 时才进亚秒级,并且只在一次 数十分钟到数小时的 soak(贴近生产的负载 以及 堆/GC、并统计误切换次数)之后。安全下限由你最坏的真实健康-leader 停顿决定,而非你希望的那个数字。
session.timeout是独立的 —— 按”集群该多快驱逐一个沉默客户端”来调它,与 leader 故障切换无关。- 上一个具备故障切换感知的客户端(
pollEgress()+EgressListener)—— 没有它,任何故障切换时间 都毫无意义。
在生产中调低故障切换时间前的检查清单
Section titled “在生产中调低故障切换时间前的检查清单”- 用的哪个 GC,它在负载下的最坏暂停是多少?(ZGC ≪ G1/Parallel。)这决定了你的下限。
- 热线程是否绑核(
isolcpus/nohz_full),还是在抢核? - archive 往哪儿 fsync —— tmpfs、本地 NVMe、还是网络存储?(磁盘卡顿会触发检测。)
- 同 AZ / 放置组,还是跨 AZ?(网络抖动会拉宽所需余量。)
- 裸金属还是 VM?(VM steal 会叠加尾部延迟。)
-
interval是否 ≤timeout的约 1/5? - 是否在生产级负载 以及堆/GC 下 soak 足够久,看到了尾部(≥ 数十分钟)?
- ingress 客户端是否处理
onNewLeader并轮询 egress? - 这个目标是被真实 SLA 撑起来的,还是 1–2 s 其实就已经够好了?
延伸阅读:Leader 放置与优雅让位、 Cluster Standby 与多 AZ HA 设计、以及 故障处置手册。
本站与 Adaptive Financial Consulting Limited 或 Aeron 项目无任何关联,未获其背书或赞助。 Aeron 是 Adaptive Financial Consulting Limited 的注册商标。
Aeron 是 Adaptive Financial Consulting Limited 在英国及其他国家/地区的商标。