跳至内容
20.1 从失败模型设计高可用

20.1 从失败模型设计高可用

高可用设计最常见的错误,是先问:

“要两台还是三台?”

正确的第一问是:

“哪些东西会以什么方式一起失败,失败后业务允许留下什么状态?”

节点数是答案的一部分,failure model 才是题目。

20.1.1 进程、主机、磁盘、网络、机房与控制面故障

故障、失效与事故

先统一三个词:

fault
  某个组件偏离预期,例如磁盘超时、进程崩溃、链路丢包

failure
  系统因此无法完成合同中的能力,例如 primary 不能提交

incident
  failure 或高风险状态进入人的响应流程

一个 fault 未必立刻成为 service failure。反过来,组件都显示 running, 客户端也可能因 DNS、pool、证书或连接风暴而无法提交。

所以失败模型的 observation point 必须从“进程是否活着”扩展到:

client -> name/VIP -> proxy -> pool -> PostgreSQL
                                  -> WAL transport/replay
Patroni -> DCS -> member health -> role transition
backup/archive -> independent recovery history

不同 failure domain

层次例子可能影响不能靠什么证明已覆盖
PostgreSQL 进程crash、OOM、assert单实例停止三个 PID 都在
OS/主机kernel panic、reboot、供电主机上全部组件三个 VM 名称
存储device loss、stall、fs corruption数据/WAL 不可读或卡死RAID 标签
网络丢包、分区、非对称路由DCS、复制、客户端视图分裂ping 一次
zone/rackswitch/PDU/机架一组主机共同失效不同 IP
site/region机房、运营商、灾害整个本地 HA 集群同城三副本
control planeetcd/API/DNS/PKI不能安全选主或发现服务PostgreSQL 可查询
client pathHAProxy/PgBouncer/driver数据库正常但业务不可用Patroni 显示 leader
human/change错 DDL、错配置、误删正确地复制错误replica healthy

每个 failure domain 至少写四件事:

detection
  谁、用什么信号、多久知道?

decision authority
  谁有权停止旧主、选择新主、接受数据风险?

containment/recovery
  restart、reroute、promote、rewind、rebuild 还是 restore?

proof
  什么证据表明业务能力恢复,而不只是组件换色?

“三节点”不是故障域证明

本章沙箱有三台 pg-test VM,但它们:

share one laptop
share one hypervisor
share host power
share much of the storage substrate
share one etcd member

因此:

[ N_{members}=3 \quad\not\Rightarrow\quad N_{independent\ failure\ domains}=3 ]

如果宿主机休眠,三台 VM、DCS、proxy 与本地备份目标可能一起消失。形式上 的 replica count 没有覆盖共同依赖。

生产设计应显式记录 placement:

member -> host -> rack/PDU -> AZ -> region
DCS member -> AZ
proxy/VIP -> network domain
backup repository -> storage/account/region
operator access -> identity/control plane

“不在同一台机器”只是最低一层。

网络分区比断网更难

完全断网容易理解;危险的是不同观察者看到不同世界:

old primary can still serve some clients
old primary cannot update DCS
replica can reach DCS and become new primary
some clients continue reaching old address

此时目标不是让两边都“尽量可用”,而是避免两个可写历史同时接受不可合并 的提交。选主只决定谁获得 authority;还必须让失去 authority 的旧主停止 提交,或从客户端路径隔离。

存储故障不只有“磁盘没了”

真实存储 fault 包括:

hard error
silent corruption
latency spike
fsync hang
capacity full
inode exhaustion
read-only remount
controller/cache failure
correlated network storage loss

一个极慢但未死的 primary 可能比 crash 更难处理:

  • health check 仍偶尔成功;
  • leader loop 得不到调度;
  • shutdown 超过租约;
  • client 请求堆积;
  • WAL sender/replay 指标变得陈旧。

因此 failure injection 不能只有 kill -9 postgres。但本章也不会冒险把这些 场景一次性塞进共享 laptop 沙箱;它们被列入 failure-model.json,明确标记为 not-injected

控制面与数据面

至少区分:

data plane
  PostgreSQL 接收查询、提交、发送和重放 WAL

control plane
  Patroni + DCS 决定 leader authority 与配置

service plane
  DNS/VIP/HAProxy/PgBouncer 把客户端带到合适角色

recovery plane
  backup、WAL archive、restore tooling

DCS 不可用时,已有 PostgreSQL 连接可能暂时工作;这不意味着能安全进行 新一轮选主。数据库进程可用和自动 HA control 可用不是同一个布尔值。

共同模式故障

一个严肃的 failure model 会问:

是否共用同一电源?
是否共用同一存储控制器或云账户?
是否共用同一错误配置?
是否由同一自动化一次性重启?
是否共用同一证书、DNS、KMS 或 IAM?
是否都依赖一个人能登录?

“每台主机都健康”无法发现下一次发布会同时把三台配置写坏。

场景卡

每个场景用同一模板:

id: host-loss-primary
trigger: primary host becomes unreachable
scope: one independent host
assumptions:
  - DCS quorum survives
  - one eligible replica survives
  - client service can reach it
observable_start: first failed client write
expected_control_action: old authority expires, eligible replica promoted
data_boundary: defined by acknowledgement/sync policy
client_completion: read-write transactions succeed through stable endpoint
stop_conditions:
  - old primary may still be writable
  - candidate lineage is ambiguous
  - DCS has no decision authority
evidence:
  - DCS/Patroni history
  - system identifier and timeline
  - client tokens
  - old member fencing/rejoin

没有 assumptions 和 stop conditions 的“演练步骤”,更像破坏脚本。

本章实际覆盖什么

九个场景中,正式观察的只有:

planned-primary-maintenance
  no component failure
  healthy current leader
  named caught-up candidate
  controlled switchover
  controlled baseline restore

client-session-loss 得到一次采样观察,但完整 routing contract 留给第 22 章。其余故障不能从本次结果反推。

20.1.2 RPO、RTO、降级目标与数据风险

RPO 是允许退回多远

令:

t_truth  = 故障前业务认可的最新可恢复事实时间
t_point  = 实际恢复点

则时间口径的实际恢复点损失可写为:

[ RPO_{actual}=t_{truth}-t_{point} ]

但 PostgreSQL 复制更常先观测 WAL byte:

[ gap_{bytes}

LSN_{primary}-LSN_{candidate} ]

两者不能直接互换。相同 1 MiB WAL:

  • 高峰可能只代表几十毫秒;
  • 低峰可能跨越很久;
  • 一条关键订单与一批可重建日志的业务损失不同。

所以 RPO 合同至少要说明:

which acknowledgement class
which failure scenario
which candidate set
time/byte/business-event measurement
whether simultaneous failures are in scope

“RPO < 1 MB”与“任何已确认订单都不会丢”不是同一句话。

RTO 从哪个时刻算到哪个时刻

一个故障路径可拆成:

[ T_{recover} =T_{detect} +T_{decide} +T_{fence} +T_{promote} +T_{route} +T_{reconnect} +T_{app} ]

不同仪表测到不同终点:

时钟开始结束能回答什么
component进程 faultPostgreSQL running进程恢复
controllease/health failure新 leader 稳定控制面迁移
serviceendpoint first fails新连接可写接入恢复
transaction业务动作发起可判断结果用户恢复
backlog故障开始积压清空完整业务恢复

patronictl 返回时间不是 application RTO。HAProxy 健康检查通过也不等于 已有 pool/session 已恢复。

计划切换没有故障检测阶段

本章正式动作由操作者在健康状态发起:

T_detect = 0 by construction
candidate selected in advance
maintenance authority already granted

所以观测的 action-to-stable ≈ 5.823 s 不能代替主机故障 RTO。后者还要 包括 detection、lease expiry、candidate decision、可能的 fencing 与 client retry。

objective、measurement 与 promise

建议用三个字段避免混淆:

objective
  事前目标,例如 sampled write gap <= 15s

observation
  本次结果,例如 6.007s

SLO
  在定义窗口和分位数上的正式承诺,例如
  99% eligible single-host failovers restore writes within 60s

一次 observation 不能建立 percentile。通过一次演练只说明:

one run <= one objective under recorded conditions

degradation target

availability 不是只有 up/down。故障期间可定义:

能力目标状态禁止状态
已有读事务可失败并重连静默读到错误 authority
新写事务短暂拒绝两个 primary 同时接受写
只读查询可从合格 replica 提供把陈旧数据冒充强一致
后台任务暂停/排队无幂等地重复
管理写人工冻结绕过服务直连旧主

一致性优先的系统宁愿短时不可写,也不接受两个历史。降级目标必须由业务 owner 接受,而不是数据库团队在事故中临时猜。

提交风险有三种客户端状态

对一次业务写:

acknowledged
  客户端收到成功;系统必须按合同保护它

rejected-before-send
  明确没有发送,可在业务规则允许时重试

outcome-unknown
  请求可能到达、可能提交,但应答丢失

网络错误或 session 被 HAProxy 关闭,只能告诉客户端“连接失败”,不能自动 证明事务回滚。盲目重试:

charge card
create order
consume coupon
increment balance

可能造成重复业务动作。

本章 probe 为每次尝试生成:

run_id + attempt_no + unique token

结果未知后不把它改成一个新 token 重做,而是在服务稳定后查 token:

present -> committed
absent  -> not present in chosen history

正式运行 25 个 unknown token 全部核对为 absent。这个结果描述本次选择的 最终历史;应用仍需决定 absent 后是否以及如何重试。

数据风险不止“丢几行”

还包括:

acknowledged commit missing
unknown commit duplicated by retry
sequence/external side effect diverged
read-your-writes broken
replica stale read drives wrong decision
two timelines receive writes
logical corruption replicated everywhere

因此 HA acceptance 必须同时观测服务可用性和数据语义。

目标卡

一个可评审的目标:

scenario: one independent primary-host loss
window: 30-day rolling
rpo:
  acknowledged critical writes: 0
  lower-tier events: <= agreed byte/time envelope
rto:
  client read-write: p99 <= 60s
degradation:
  writes may be rejected
  stale reads must be labeled
  duplicate business actions forbidden
dependencies:
  DCS quorum, candidate, proxy, DNS, identity survive
measurement:
  external transaction probe + token reconciliation

是否能实现,要由同步策略、failure placement、服务路径和客户端能力共同 回答。

20.1.3 高可用不等于备份,也不等于零数据丢失

三种能力回答不同问题

能力主要问题典型机制无法独自处理
HA当前 primary 不能服务怎么办replica、election、routing逻辑误删、历史恢复
backup/recovery要回到过去或异地怎么办base backup、WAL archive、PITR秒级接管
data protection哪些确认写必须存在几份sync policy、placement、storage客户端重连

replica 是当前历史的追随者;backup 是可选择的历史恢复材料。

replica 会忠实复制错误

以下操作通常会进入 WAL 并传播:

DROP TABLE orders;
DELETE FROM orders;
UPDATE orders SET status = 'paid';  -- missing predicate

物理副本不会判断这是事故。它的健康意味着复制系统工作,而不是数据仍符合 业务真相。

可以用 delayed replica 降低某些操作错误风险,但它仍不是完整 backup 策略:

  • 延迟窗口有限;
  • 主机/账户/自动化可能共故障;
  • 到点仍会重放错误;
  • 恢复流程与一致性仍需验证;
  • 不能替代离线/跨域历史和 retention。

第 21 章会把 restore proof 作为独立 gate。

异步复制不承诺零 RPO

PostgreSQL 流复制默认异步。primary 可以先向客户端确认,再把最新 WAL 传给 standby。若 primary 的未传输尾部永久丢失:

client saw COMMIT
candidate never received that WAL
candidate promoted
acknowledged transaction absent

PostgreSQL 18 官方文档 明确说明异步 log shipping 存在这类窗口;streaming 可以缩小窗口,不会 用“通常很小”把它变成零。

本章实际配置:

synchronous_mode=false
synchronous_mode_strict=false
synchronous_standby_names=''
pg_stat_replication.sync_state=async

所以即使一次健康 switchover 所有 acknowledged token 都存在,也只能说明:

the named healthy candidate caught up for this planned transition

不能说明:

an unplanned catastrophic primary loss has zero RPO

同步复制也不是魔法

同步提交提升指定失败范围内的 durability,但 guarantee 依赖:

which standby acknowledged
what acknowledgement level
candidate eligibility
simultaneous failure assumptions
failure-domain independence
storage durability
manual override policy

如果 primary 与当前同步 standby 同时失效,或操作者强制提升一个落后 candidate,仍可能丢失 acknowledged history。Patroni 文档也把 synchronous_mode 的数据保护与 write availability 代价放在一起解释, 而不是承诺任何故障组合都零损失。

backup 也不是“文件存在”

备份成立至少需要:

complete base backup
required WAL retained
catalog/metadata intact
credentials and keys available
target infrastructure available
restore procedure tested
business validation passed

本章看到 archive_mode=on 与归档统计,不会因此宣布 PITR 已通过。 archive 命令是否持续成功、repository 是否独立、恢复链是否完整,全部留给 第 21 章实测。

能力矩阵

场景HA replicabackup/PITRapplication design
primary 进程 crash主要兜底reconnect
primary host 永久损失主要兜底unknown outcome
整个机房损失取决于跨域主要regional failover
误删表会复制错误主要guard/repair
duplicate retry无法识别业务重复不能预防idempotency
数据静默错误可能传播历史/校验invariant
ransomware/credential compromise可能同受影响immutable/offlineidentity/response

三条生产阻断线

以下任一成立,就不能把 HA gate 判为生产通过:

failure domains not mapped
acknowledged-write policy not defined
old primary isolation not demonstrated

同样:

restore not rehearsed -> recovery gate pending
client retry undefined -> service gate pending
secret/TLS unresolved  -> security gate pending

一个 gate 通过不能替别的 gate 签字。

本节检查

请为自己的服务写出:

  1. 六类 failure domain 与共同依赖;
  2. 一个 eligible scenario 的 RPO/RTO 起止点;
  3. 允许的降级和绝不允许的状态;
  4. acknowledged / rejected / unknown 三种提交结果的处理;
  5. HA、backup 与 application 各自 owner;
  6. 三条 stop condition。

如果答案仍是“我们有三台,所以高可用”,这一节还没有完成。

小结

HA design starts with loss, not topology
RPO/RTO need scenario and observation points
planned maintenance is not failure detection
outcome-unknown is a business state
replication follows current history
backup preserves selectable history
exceptions block claims

权威参考


返回本章目录 · 下一节:物理流复制 · 查看全书目录 · 查看索引中心

最后更新于