这是本节的多页打印视图。
点击此处打印 .
返回本页常规视图 .
故障切换模型 详细分析三种经典故障检测/恢复路径下,最差,最优,平均 RTO 的计算逻辑与结果
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 手动触发
但是在 RTO 计算上,最终所有故障都会收敛到两条路径上,本节深入探讨了这两种情况下的 RTO 上下限与均值。
flowchart LR
A([主库故障]) --> B{Patroni<br/>检测到?}
B -->|PG崩溃| C[尝试本地重启]
B -->|节点宕机| D[等待 TTL 过期]
C -->|成功| E([本地恢复])
C -->|失败/超时| F[释放 Leader 锁]
D --> F
F --> G[从库竞选]
G --> H[执行 Promote]
H --> I[HAProxy 感知]
I --> J([服务恢复])
style A fill:#dc3545,stroke:#b02a37,color:#fff
style E fill:#198754,stroke:#146c43,color:#fff
style J fill:#198754,stroke:#146c43,color:#fff 1 - 被动故障切换 节点宕机,导致领导者租约过期触发集群领导竞选的故障路径
RTO 时序图
故障模型 项目 最好 最坏 平均 说明 租约过期 ttl - loopttlttl - loop/2最好:即将刷新时宕机 最坏:刚刷新完就宕机 从库检测 0looploop / 2最好:恰好在检测点 最坏:刚错过检测点 抢锁提拔 021最好:直接抢锁提升 最坏: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/2T e x p i r e = { t t l − l o o p 最好 t t l − l o o p / 2 平均 t t l 最坏 T_{expire} = \begin{cases}
ttl - loop & \text{最好} \\
ttl - loop/2 & \text{平均} \\
ttl & \text{最坏}
\end{cases} T e x p i re = ⎩ ⎨ ⎧ ttl − l oo p ttl − l oo p /2 ttl 最好 平均 最坏 阶段 2:从库检测 从库在 loop_wait 周期醒来后检查 DCS 中的 Leader Key 状态。
时间线:
租约过期 从库醒来
| |
|←── 0~loop ─→|
最好情况 :租约过期时从库恰好醒来,等待 0最坏情况 :租约过期后从库刚进入睡眠,等待 loop平均情况 :loop/2T d e t e c t = { 0 最好 l o o p / 2 平均 l o o p 最坏 T_{detect} = \begin{cases}
0 & \text{最好} \\
loop/2 & \text{平均} \\
loop & \text{最坏}
\end{cases} T d e t ec t = ⎩ ⎨ ⎧ 0 l oo p /2 l oo p 最好 平均 最坏 阶段 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 常数开销T e l e c t = { 0.1 最好 1 平均 2 最坏 T_{elect} = \begin{cases}
0.1 & \text{最好} \\
1 & \text{平均} \\
2 & \text{最坏}
\end{cases} T e l ec t = ⎩ ⎨ ⎧ 0.1 1 2 最好 平均 最坏 阶段 4:健康检查 HAProxy 检测新主库上线,需要连续 rise 次健康检查成功。
检测时序:
新主提升 首次检查 第二次检查 第三次检查(UP)
| | | |
|←─ 0~inter ─→|←─ fast ─→|←─ fast ─→|
最好情况 :新主提升时恰好赶上检查,(rise-1) × fastinter最坏情况 :新主提升后刚错过检查,(rise-1) × fastinter + inter平均情况 :(rise-1) × fastinter + inter/2T h a p r o x y = { ( r i s e − 1 ) × f a s t i n t e r 最好 ( r i s e − 1 ) × f a s t i n t e r + i n t e r / 2 平均 ( r i s e − 1 ) × f a s t i n t e r + i n t e r 最坏 T_{haproxy} = \begin{cases}
(rise-1) \times fastinter & \text{最好} \\
(rise-1) \times fastinter + inter/2 & \text{平均} \\
(rise-1) \times fastinter + inter & \text{最坏}
\end{cases} T ha p ro x y = ⎩ ⎨ ⎧ ( r i se − 1 ) × f a s t in t er ( r i se − 1 ) × f a s t in t er + in t er /2 ( r i se − 1 ) × f a s t in t er + in t er 最好 平均 最坏 RTO 公式 将各阶段时间相加,得到总 RTO:
最好情况
R T O m i n = t t l − l o o p + 0.1 + ( r i s e − 1 ) × f a s t i n t e r RTO_{min} = ttl - loop + 0.1 + (rise-1) \times fastinter RT O min = ttl − l oo p + 0.1 + ( r i se − 1 ) × f a s t in t er 平均情况
R T O a v g = t t l + 1 + i n t e r / 2 + ( r i s e − 1 ) × f a s t i n t e r RTO_{avg} = ttl + 1 + inter/2 + (rise-1) \times fastinter RT O a vg = ttl + 1 + in t er /2 + ( r i se − 1 ) × f a s t in t er 最坏情况
R T O m a x = t t l + l o o p + 2 + i n t e r + ( r i s e − 1 ) × f a s t i n t e r RTO_{max} = ttl + loop + 2 + inter + (rise-1) \times fastinter RT O ma x = ttl + l oo p + 2 + in t er + ( r i se − 1 ) × f a s t in t er 模型计算 将四种 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 / 2025 / 27 / 3050 / 55 / 60100 / 110 / 120从库检测 0 / 3 / 50 / 3 / 50 / 5 / 100 / 10 / 20抢锁提拔 0 / 1 / 20 / 1 / 20 / 1 / 20 / 1 / 2健康检查 1 / 2 / 22 / 3 / 43 / 5 / 64 / 6 / 8总计 16 / 23 / 2927 / 34 / 4153 / 66 / 78104 / 127 / 150
2 - 主动故障检测 PostgreSQL 主库进程崩溃,Patroni 存活并尝试重启,超时后触发故障切换的路径
RTO 时序图
故障模型 项目 最好 最坏 平均 说明 故障检测 0looploop/2最好:PG 恰好在检测前崩溃 最坏:PG 刚检测完就崩溃 重启超时 0startstart最好:PG 瞬间自愈 最坏:等满 start 超时才释放租约 从库检测 0looploop/2最好:恰好在检测点 最坏:刚错过检测点 抢锁提拔 021最好:直接抢锁提升 最坏: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/2T d e t e c t = { 0 最好 l o o p / 2 平均 l o o p 最坏 T_{detect} = \begin{cases}
0 & \text{最好} \\
loop/2 & \text{平均} \\
loop & \text{最坏}
\end{cases} T d e t ec t = ⎩ ⎨ ⎧ 0 l oo p /2 l oo p 最好 平均 最坏 阶段 2:重启超时 Patroni 检测到 PG 崩溃后,会尝试重启 PostgreSQL。此阶段有两种可能的结果:
时间线:
检测到崩溃 尝试重启 重启成功/超时
| | |
|←──── 0 ~ start ────────→|
路径 A:自愈成功 (最好情况)
PG 成功重启,服务恢复 不触发故障切换,RTO 极短 等待时间:0(相对于 Failover 路径) 路径 B:需要 Failover (平均/最坏情况)
等待 primary_start_timeout 超时后 PG 仍未恢复 Patroni 主动释放 Leader Key 等待时间:start T r e s t a r t = { 0 最好(自愈成功) s t a r t 平均(需要 Failover) s t a r t 最坏 T_{restart} = \begin{cases}
0 & \text{最好(自愈成功)} \\
start & \text{平均(需要 Failover)} \\
start & \text{最坏}
\end{cases} T res t a r t = ⎩ ⎨ ⎧ 0 s t a r t s t a r t 最好(自愈成功) 平均(需要 Failover ) 最坏 注意 :平均情况假设需要进行故障切换。如果 PG 能够快速自愈,则整体 RTO 会大幅降低。
阶段 3:从库检测 从库在 loop_wait 周期醒来后检查 DCS 中的 Leader Key 状态。当主库 Patroni 释放 Leader Key 后,从库发现后开始竞选。
时间线:
租约释放 从库醒来
| |
|←── 0~loop ─→|
最好情况 :租约释放时从库恰好醒来,等待 0最坏情况 :租约释放后从库刚进入睡眠,等待 loop平均情况 :loop/2T s t a n d b y = { 0 最好 l o o p / 2 平均 l o o p 最坏 T_{standby} = \begin{cases}
0 & \text{最好} \\
loop/2 & \text{平均} \\
loop & \text{最坏}
\end{cases} T s t an d b y = ⎩ ⎨ ⎧ 0 l oo p /2 l oo p 最好 平均 最坏 阶段 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 常数开销T e l e c t = { 0.1 最好 1 平均 2 最坏 T_{elect} = \begin{cases}
0.1 & \text{最好} \\
1 & \text{平均} \\
2 & \text{最坏}
\end{cases} T e l ec t = ⎩ ⎨ ⎧ 0.1 1 2 最好 平均 最坏 阶段 5:健康检查 HAProxy 检测新主库上线,需要连续 rise 次健康检查成功。
检测时序:
新主提升 首次检查 第二次检查 第三次检查(UP)
| | | |
|←─ 0~inter ─→|←─ fast ─→|←─ fast ─→|
最好情况 :新主提升时恰好赶上检查,(rise-1) × fastinter最坏情况 :新主提升后刚错过检查,(rise-1) × fastinter + inter平均情况 :(rise-1) × fastinter + inter/2T h a p r o x y = { ( r i s e − 1 ) × f a s t i n t e r 最好 ( r i s e − 1 ) × f a s t i n t e r + i n t e r / 2 平均 ( r i s e − 1 ) × f a s t i n t e r + i n t e r 最坏 T_{haproxy} = \begin{cases}
(rise-1) \times fastinter & \text{最好} \\
(rise-1) \times fastinter + inter/2 & \text{平均} \\
(rise-1) \times fastinter + inter & \text{最坏}
\end{cases} T ha p ro x y = ⎩ ⎨ ⎧ ( r i se − 1 ) × f a s t in t er ( r i se − 1 ) × f a s t in t er + in t er /2 ( r i se − 1 ) × f a s t in t er + in t er 最好 平均 最坏 RTO 公式 将各阶段时间相加,得到总 RTO:
最好情况 (PG 瞬间自愈)
R T O m i n = 0 + 0 + 0 + 0.1 + ( r i s e − 1 ) × f a s t i n t e r ≈ ( r i s e − 1 ) × f a s t i n t e r RTO_{min} = 0 + 0 + 0 + 0.1 + (rise-1) \times fastinter \approx (rise-1) \times fastinter RT O min = 0 + 0 + 0 + 0.1 + ( r i se − 1 ) × f a s t in t er ≈ ( r i se − 1 ) × f a s t in t er 平均情况 (需要 Failover)
R T O a v g = l o o p + s t a r t + 1 + i n t e r / 2 + ( r i s e − 1 ) × f a s t i n t e r RTO_{avg} = loop + start + 1 + inter/2 + (rise-1) \times fastinter RT O a vg = l oo p + s t a r t + 1 + in t er /2 + ( r i se − 1 ) × f a s t in t er 最坏情况
R T O m a x = l o o p × 2 + s t a r t + 2 + i n t e r + ( r i s e − 1 ) × f a s t i n t e r RTO_{max} = loop \times 2 + start + 2 + inter + (rise-1) \times fastinter RT O ma x = l oo p × 2 + s t a r t + 2 + in t er + ( r i se − 1 ) × f a s t in t er 模型计算 将四种 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 / 50 / 3 / 50 / 5 / 100 / 10 / 20重启超时 0 / 15 / 150 / 25 / 250 / 45 / 450 / 95 / 95从库检测 0 / 3 / 50 / 3 / 50 / 5 / 100 / 10 / 20抢锁提拔 0 / 1 / 20 / 1 / 20 / 1 / 20 / 1 / 2健康检查 1 / 2 / 22 / 3 / 43 / 5 / 64 / 6 / 8总计 1 / 24 / 292 / 35 / 413 / 61 / 734 / 122 / 145
与被动故障对比 阶段 主动故障(PG 崩溃) 被动故障(节点宕机) 说明 检测机制 Patroni 主动检测 TTL 被动过期 主动检测更快发现故障 核心等待 startttlstart 通常小于 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 可以极短。