这是本节的多页打印视图。
点击此处打印.
返回本页常规视图.
故障切换模型
详细分析三种经典故障检测/恢复路径下,最差,最优,平均 RTO 的计算逻辑与结果
在 Patroni 高可用模型,有五条典型的故障路径,本节将分情况讨论每种情况下的时序模型与 RTO 计算方式
| 检测机制 |
检测主体 |
检测信号 |
典型故障 |
主要来源 |
| 被动检测 |
DCS (etcd) |
leader key TTL 到期 |
网络分区,节点崩溃,Patroni 宕机 |
ttl |
| 主动探测 |
Patroni 领导者 |
pg_isready 失败 |
PG 进程崩溃 |
priamry_start_timeout |
| 手动触发 |
运维人员 |
patronictl switchover/failover |
主动切换命令,维护操作 |
haproxy_up |
| # |
路径 |
常见故障 |
检测机制 |
关键参数 |
RTO 特点 |
发生概率 |
| 2 |
租约过期 |
节点宕机,Patroni 崩溃 |
TTL 过期 |
ttl |
稳定可预测 |
中等 |
| 1 |
主动检测 |
PG 进程崩溃 |
loop_wait |
primary_start_timeout |
可能最长(有恢复尝试) |
中等 |
| 3 |
网络分区 |
主库 - DCS 网络中断,防火墙,交换机故障 |
TTL 过期 |
retry_timeout |
与被动检测相同,但有降级时间点 |
罕见 |
| 4 |
看门狗 |
节点假活,Patroni 假活 |
硬件定时器 |
ttl / safety_margin |
|
罕见 |
| 5 |
手动触发 |
switchover / failover |
人工触发 |
haproxy options |
最短最可控,由 HAProxy 决定 |
人工 |
检测到故障之后,恢复过程的 RTO 还需要包括恢复策略的耗时,以及 HAProxy 健康检查的耗时。
Patroni 故障按故障对象分类可以分为以下 10 类,按照检测路径不同,可以进一步归纳为五类,在本节内详细展开。
| # |
故障场景 |
描述 |
最终走哪条路径 |
| 1 |
PG 进程崩溃 |
crash、OOM killed |
主动检测 |
| 2 |
PG 拒绝连接 |
max_connections |
主动检测 |
| 3 |
PG 假活 |
进程在但无响应 |
主动检测 (检测超时) |
| 4 |
Patroni 进程崩溃 |
kill -9、OOM |
被动检测 |
| 5 |
Patroni 假活 |
进程在但卡住 |
Watchdog |
| 6 |
节点宕机 |
断电、硬件故障 |
被动检测 |
| 7 |
节点假活 |
IO hang、CPU 饥饿 |
Watchdog |
| 8 |
主库 ↔ DCS 网络中断 |
防火墙、交换机故障 |
网络分区 |
| 9 |
存储故障 |
磁盘坏、磁盘满、挂载失败 |
主动检测 或 Watchdog |
| 10 |
手动切换 |
Switchover/Failover |
手动触发 |
1 - 过期故障
节点宕机,导致领导者租约过期触发集群领导竞选的故障路径
RTO 时序图
故障模型
| 项目 |
最好 |
最坏 |
平均 |
说明 |
| 租约过期 |
ttl - loop |
ttl |
ttl - loop/2 |
最好:即将刷新时宕机 最坏:刚刷新完就宕机 |
| 从库检测 |
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 随节点一起宕机,无法主动释放 Leader Key。
DCS 中的租约只能等待 TTL 自然过期后触发集群选举。
时序分析
阶段 1:租约过期
Patroni 主库会在每个 loop_wait 周期刷新 Leader Key,将 TTL 重置为配置值。
时间线:
t-loop t t+ttl-loop t+ttl
| | | |
上次刷新 故障发生 最好情况 最坏情况
|←── loop ──→| | |
|←──────────── ttl ─────────────────────→|
- 最好情况:故障发生在即将刷新租约之前(距上次刷新已过
loop),剩余 TTL = ttl - loop
- 最坏情况:故障发生在刚刷新租约之后,需等待完整
ttl
- 平均情况:
ttl - loop/2
Texpire=⎩⎨⎧ttl−loopttl−loop/2ttl最好平均最坏阶段 2:从库检测
从库在 loop_wait 周期醒来后检查 DCS 中的 Leader Key 状态。
时间线:
租约过期 从库醒来
| |
|←── 0~loop ─→|
- 最好情况:租约过期时从库恰好醒来,等待
0
- 最坏情况:租约过期后从库刚进入睡眠,等待
loop
- 平均情况:
loop/2
Tdetect=⎩⎨⎧0loop/2loop最好平均最坏阶段 3:抢锁提拔
从库发现 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 常数开销
Telect=⎩⎨⎧0.112最好平均最坏阶段 4:健康检查
HAProxy 检测新主库上线,需要连续 rise 次健康检查成功。
检测时序:
新主提升 首次检查 第二次检查 第三次检查(UP)
| | | |
|←─ 0~inter ─→|←─ fast ─→|←─ fast ─→|
- 最好情况:新主提升时恰好赶上检查,
(rise-1) × fastinter
- 最坏情况:新主提升后刚错过检查,
(rise-1) × fastinter + inter
- 平均情况:
(rise-1) × fastinter + inter/2
Thaproxy=⎩⎨⎧(rise−1)×fastinter(rise−1)×fastinter+inter/2(rise−1)×fastinter+inter最好平均最坏
RTO 公式
将各阶段时间相加,得到总 RTO:
最好情况
RTOmin=ttl−loop+0.1+(rise−1)×fastinter平均情况
RTOavg=ttl+1+inter/2+(rise−1)×fastinter最坏情况
RTOmax=ttl+loop+2+inter+(rise−1)×fastinter
模型计算
将四种 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 |
| 租约过期 |
15 / 17 / 20 |
25 / 27 / 30 |
50 / 55 / 60 |
100 / 110 / 120 |
| 从库检测 |
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 |
| 总计 |
16 / 23 / 29 |
27 / 34 / 41 |
53 / 66 / 78 |
104 / 127 / 150 |
2 - 崩溃故障
PostgreSQL 主库进程崩溃,Patroni 存活并尝试重启,超时后触发故障切换的路径
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
Tdetect=⎩⎨⎧0loop/2loop最好平均最坏阶段 2:重启超时
Patroni 检测到 PG 崩溃后,会尝试重启 PostgreSQL。此阶段有两种可能的结果:
时间线:
检测到崩溃 尝试重启 重启成功/超时
| | |
|←──── 0 ~ start ────────→|
路径 A:自愈成功(最好情况)
- PG 成功重启,服务恢复
- 不触发故障切换,RTO 极短
- 等待时间:
0(相对于 Failover 路径)
路径 B:需要 Failover(平均/最坏情况)
- 等待
primary_start_timeout 超时后 PG 仍未恢复
- Patroni 主动释放 Leader Key
- 等待时间:
start
Trestart=⎩⎨⎧0startstart最好(自愈成功)平均(需要 Failover)最坏
注意:平均情况假设需要进行故障切换。如果 PG 能够快速自愈,则整体 RTO 会大幅降低。
阶段 3:从库检测
从库在 loop_wait 周期醒来后检查 DCS 中的 Leader Key 状态。当主库 Patroni 释放 Leader Key 后,从库发现后开始竞选。
时间线:
租约释放 从库醒来
| |
|←── 0~loop ─→|
- 最好情况:租约释放时从库恰好醒来,等待
0
- 最坏情况:租约释放后从库刚进入睡眠,等待
loop
- 平均情况:
loop/2
Tstandby=⎩⎨⎧0loop/2loop最好平均最坏阶段 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 常数开销
Telect=⎩⎨⎧0.112最好平均最坏阶段 5:健康检查
HAProxy 检测新主库上线,需要连续 rise 次健康检查成功。
检测时序:
新主提升 首次检查 第二次检查 第三次检查(UP)
| | | |
|←─ 0~inter ─→|←─ fast ─→|←─ fast ─→|
- 最好情况:新主提升时恰好赶上检查,
(rise-1) × fastinter
- 最坏情况:新主提升后刚错过检查,
(rise-1) × fastinter + inter
- 平均情况:
(rise-1) × fastinter + inter/2
Thaproxy=⎩⎨⎧(rise−1)×fastinter(rise−1)×fastinter+inter/2(rise−1)×fastinter+inter最好平均最坏
RTO 公式
将各阶段时间相加,得到总 RTO:
最好情况(PG 瞬间自愈)
RTOmin=0+0+0+0.1+(rise−1)×fastinter≈(rise−1)×fastinter平均情况(需要 Failover)
RTOavg=loop+start+1+inter/2+(rise−1)×fastinter最坏情况
RTOmax=loop×2+start+2+inter+(rise−1)×fastinter
模型计算
将四种 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 可以极短。