主动故障检测
RTO 时序图
故障模型
| 项目 | 最好 | 最坏 | 平均 | 说明 |
|---|---|---|---|---|
| 故障检测 | 0 | loop | loop/2 | 最好:PG 恰好在检测前崩溃 最坏:PG 刚检测完就崩溃 |
| 重启超时 | 0 | start | start | 最好:PG 瞬间自愈 最坏:等满 start 超时才释放租约 |
| 从库检测 | 0 | loop | loop/2 | 最好:恰好在检测点 最坏:刚错过检测点 |
| 抢锁提拔 | 0 | 2 | 1 | 最好:直接抢锁提升 最坏:API 超时 + Promote |
| 健康检查 | (rise-1) × fastinter | (rise-1) × fastinter + inter | (rise-1) × fastinter + inter/2 | 最好:检查前状态变化 最坏:检查后瞬间状态变化 |
主动故障与被动故障的核心区别:
| 场景 | Patroni 状态 | 租约处理 | 主要等待时间 |
|---|---|---|---|
| 主动故障(PG 崩溃) | 存活,健康 | 主动尝试重启 PG,超时后释放租约 | primary_start_timeout |
| 被动故障(节点宕机) | 随节点一起死亡 | 无法主动释放,只能等待 TTL 过期 | ttl |
在主动故障场景中,Patroni 仍然存活,能够主动检测到 PG 崩溃并尝试重启。 如果重启成功,服务自愈;如果超时仍未恢复,Patroni 会主动释放 Leader Key,触发集群选举。
时序分析
阶段 1:故障检测
Patroni 在每个 loop_wait 周期检查 PostgreSQL 状态(通过 pg_isready 或检查进程)。
时间线:
上次检测 PG崩溃 下次检测
| | |
|←── 0~loop ─→| |
- 最好情况:PG 恰好在 Patroni 检测前崩溃,立即被发现,等待
0 - 最坏情况:PG 刚检测完就崩溃,需等待下一个周期,等待
loop - 平均情况:
loop/2
阶段 2:重启超时
Patroni 检测到 PG 崩溃后,会尝试重启 PostgreSQL。此阶段有两种可能的结果:
时间线:
检测到崩溃 尝试重启 重启成功/超时
| | |
|←──── 0 ~ start ────────→|
路径 A:自愈成功(最好情况)
- PG 成功重启,服务恢复
- 不触发故障切换,RTO 极短
- 等待时间:
0(相对于 Failover 路径)
路径 B:需要 Failover(平均/最坏情况)
- 等待
primary_start_timeout超时后 PG 仍未恢复 - Patroni 主动释放 Leader Key
- 等待时间:
start
注意:平均情况假设需要进行故障切换。如果 PG 能够快速自愈,则整体 RTO 会大幅降低。
阶段 3:从库检测
从库在 loop_wait 周期醒来后检查 DCS 中的 Leader Key 状态。当主库 Patroni 释放 Leader Key 后,从库发现后开始竞选。
时间线:
租约释放 从库醒来
| |
|←── 0~loop ─→|
- 最好情况:租约释放时从库恰好醒来,等待
0 - 最坏情况:租约释放后从库刚进入睡眠,等待
loop - 平均情况:
loop/2
阶段 4:抢锁提拔
从库发现 Leader Key 空缺后,开始竞选过程,获得 Leader Key 的从库执行 pg_ctl promote,将自己提升为新主库。
- 通过 Rest API,并行发起查询,查询各从库的复制位置,通常 10ms,硬编码 2 秒超时。
- 比较 WAL 位置,确定最优候选,各从库尝试创建 Leader Key(CAS 原子操作)
- 执行
pg_ctl promote提升自己为主库(很快,通常忽略不计)
选举流程:
从库A ──→ 查询复制位置 ──→ 比较 ──→ 尝试抢锁 ──→ 成功
从库B ──→ 查询复制位置 ──→ 比较 ──→ 尝试抢锁 ──→ 失败
- 最好情况:单从库或直接抢到锁并提升,常数开销
0.1s - 最坏情况:DCS API 调用超时:
2s - 平均情况:
1s常数开销
阶段 5:健康检查
HAProxy 检测新主库上线,需要连续 rise 次健康检查成功。
检测时序:
新主提升 首次检查 第二次检查 第三次检查(UP)
| | | |
|←─ 0~inter ─→|←─ fast ─→|←─ fast ─→|
- 最好情况:新主提升时恰好赶上检查,
(rise-1) × fastinter - 最坏情况:新主提升后刚错过检查,
(rise-1) × fastinter + inter - 平均情况:
(rise-1) × fastinter + inter/2
RTO 公式
将各阶段时间相加,得到总 RTO:
最好情况(PG 瞬间自愈)
平均情况(需要 Failover)
最坏情况
模型计算
将四种 RTO 模型的参数带入上面的公式:
pg_rto_plan: # [ttl, loop, retry, start, margin, inter, fastinter, downinter, rise, fall]
fast: [ 20 ,5 ,5 ,15 ,5 ,'1s' ,'0.5s' ,'1s' ,3 ,3 ] # rto < 30s
norm: [ 30 ,5 ,10 ,25 ,5 ,'2s' ,'1s' ,'2s' ,3 ,3 ] # rto < 45s
safe: [ 60 ,10 ,20 ,45 ,10 ,'3s' ,'1.5s' ,'3s' ,3 ,3 ] # rto < 90s
wide: [ 120 ,20 ,30 ,95 ,15 ,'4s' ,'2s' ,'4s' ,3 ,3 ] # rto < 150s
四种模式计算结果(单位:秒,格式:min / avg / max)
| 阶段 | fast | norm | safe | wide |
|---|---|---|---|---|
| 故障检测 | 0 / 3 / 5 | 0 / 3 / 5 | 0 / 5 / 10 | 0 / 10 / 20 |
| 重启超时 | 0 / 15 / 15 | 0 / 25 / 25 | 0 / 45 / 45 | 0 / 95 / 95 |
| 从库检测 | 0 / 3 / 5 | 0 / 3 / 5 | 0 / 5 / 10 | 0 / 10 / 20 |
| 抢锁提拔 | 0 / 1 / 2 | 0 / 1 / 2 | 0 / 1 / 2 | 0 / 1 / 2 |
| 健康检查 | 1 / 2 / 2 | 2 / 3 / 4 | 3 / 5 / 6 | 4 / 6 / 8 |
| 总计 | 1 / 24 / 29 | 2 / 35 / 41 | 3 / 61 / 73 | 4 / 122 / 145 |
与被动故障对比
| 阶段 | 主动故障(PG 崩溃) | 被动故障(节点宕机) | 说明 |
|---|---|---|---|
| 检测机制 | Patroni 主动检测 | TTL 被动过期 | 主动检测更快发现故障 |
| 核心等待 | start | ttl | start 通常小于 ttl,但需要额外的故障检测时间 |
| 租约处理 | 主动释放 | 被动过期 | 主动释放更及时 |
| 自愈可能 | ✅ 有 | ❌ 无 | 主动检测可尝试本地恢复 |
RTO 对比(平均情况):
| 模式 | 主动故障(PG 崩溃) | 被动故障(节点宕机) | 差异 |
|---|---|---|---|
| fast | 24s | 23s | +1s |
| norm | 35s | 34s | +1s |
| safe | 61s | 66s | -5s |
| wide | 122s | 127s | -5s |
分析:在
fast和norm模式下,主动故障的 RTO 略高于被动故障,因为需要等待primary_start_timeout(start); 但在safe和wide模式下,由于start < ttl - loop,主动故障反而更快。 不过主动故障有自愈的可能性,最好情况下 RTO 可以极短。