自动故障转移方案
手工故障转移(pg_ctl promote)是所有高可用的底层原语,但它依赖人在场:判定主库不可用、选择候选备库、执行提升、切换流量、善后旧主库——每个环节都要几分钟到几十分钟。对多数业务而言,这个 RTO 是不可接受的。自动故障转移方案把这套流程交给软件:故障发生后的秒级到分钟级内,由工具自动完成判定、提升与流量切换。
但自动化不是免费的。自动判定「主库死了还是联系不上」引入了误判风险;自动提升引入了脑裂风险;自动流量切换引入了「切到一半」的中间状态。因此成熟的自动故障转移方案的核心设计,不是「怎么提升」,而是「怎么确认只有一个人有资格提升」——这就是共识层(DCS)与 fencing 的意义。
本页先拆解自动故障转移的组成结构,再对比主流方案(Patroni、repmgr、pg_auto_failover、Pgpool-II、云托管),重点讲清共识层的作用、脑裂与 fencing、流量切换层的设计,以及监控、演练与选型建议。手工流程的细节(提升命令、时间线、pg_rewind)已在前几页展开,本页聚焦「自动化如何组织这些原语」。
前置知识
建议先阅读:时间线与时间线历史
基础概念
自动故障转移引入了一批分布式系统的术语。先把它们与 PostgreSQL 原生概念对应起来,看方案文档时才不会迷失。
| 概念 | 英文 | 说明 |
|---|---|---|
| DCS | Distributed Configuration Store | 分布式配置存储(etcd / Consul / ZooKeeper),提供共识与选主 |
| 领导者租约 | leader lease | 主库通过 DCS 持有的「任职凭证」,到期未续约即失格 |
| Fencing | 隔离 | 确保旧主库无法继续作为写入点,是防脑裂的核心手段 |
| STONITH | Shoot The Other Node In The Head | fencing 的极端形式:直接关掉旧节点电源 |
| 脑裂 | split-brain | 两个节点同时作为主库接受写入 |
| 仲裁 | quorum | 超过半数成员同意才允许关键动作(如提升) |
| 健康检查 | health check | 周期性探测主库可用性的机制 |
| 降级 | degraded | 集群失去部分冗余但仍在服务的状态(如仅剩主库无备库) |
| 委托 IP | delegate IP | 由 watchdog 组件管理的虚拟 IP,随主库漂移 |
| 提升候选 | candidate | 被选中执行提升的备库 |
自动故障转移的组成结构
任何自动故障转移方案,都可以拆成四个组件。理解这个框架,就能把任何新方案快速归位:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 1. 健康检查 │ → │ 2. 仲裁/选举 │ → │ 3. 提升 │ → │ 4. 流量切换 │
│ 探测主库状态 │ │ 决定谁有资格 │ │ promote │ │ VIP/DNS/代理 │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘- 健康检查:周期性探测主库(连接、只读探针、磁盘、进程),持续 N 次失败才判定故障,排除瞬时抖动;
- 仲裁 / 选举:这是自动方案与手工方案的本质区别。谁判定故障、谁授权提升,需要一个超越单个数据库节点的裁决者——即共识层;
- 提升:本质还是前几页讲的
pg_ctl promote/pg_promote(),自动化只是包装了前置检查与善后(含 pg_rewind); - 流量切换:让客户端找到新主库。VIP、HAProxy、DNS、应用内多地址,各有取舍。
DCS 是什么
DCS(Distributed Configuration Store)是独立于数据库之外的一组小集群(etcd 通常 3 或 5 个节点),提供两样关键能力:分布式锁与带租约的键值存储。数据库节点把「我是主库」这个事实写入 DCS 并持续续租;租约到期未续约,DCS 上的主库记录自动消失,其余节点才有资格竞选。
关键性质:DCS 自己用多数派(quorum)保证一致性。3 节点的 etcd 挂 1 个仍可工作,挂 2 个就整体只读——此时没有任何数据库节点能拿到主库租约,集群宁可「不可写」也不会「双主」。这个「宁可不可用也不脑裂」的取舍,正是共识层的价值所在。
主流方案一图流
┌─────────────────────────────────────────┐
│ DCS (etcd ×3) │
└───────┬─────────────┬─────────────┬─────┘
│ │ │
┌──────────┴───┐ ┌───────┴──────┐ ┌────┴─────────┐
│ Patroni │ │ Patroni │ │ Patroni │
│ node1 (主) │ │ node2 (备) │ │ node3 (备) │
└──────┬───────┘ └──────┬───────┘ └────┬─────────┘
│ :8008 │ :8008 │ :8008
┌────┴────────────────┴──────────────┴────┐
│ HAProxy(健康检查路由写流量) │
└──────────────────┬───────────────────────┘
│ :5432
应用语法与用法
自动故障转移方案的「语法」是各工具的配置文件与命令行。这里给出三个主流方案的最小骨架,让你对部署形态有直观认识;完整配置以各自官方文档为准。
Patroni 最小配置
Patroni 用 YAML 描述集群,节点之间通过 DCS 感知彼此:
# patroni.yml(三个节点各一份,name 各不相同)
scope: pg-cluster
name: node1
restapi:
listen: 0.0.0.0:8008
connect_address: 10.0.0.10:8008
etcd3:
hosts: 10.0.1.10:2379,10.0.1.11:2379,10.0.1.12:2379
bootstrap:
pg_hba:
- host replication replicator 10.0.0.0/24 scram-sha-256
- host all all 10.0.0.0/24 scram-sha-256
postgresql:
listen: 0.0.0.0:5432
connect_address: 10.0.0.10:5432
data_dir: /var/lib/postgresql/18/main
authentication:
superuser:
username: postgres
password: 'Ch4ngeM3_Str0ng'
replication:
username: replicator
password: 'Repl_Ch4ngeM3'# 启动与管理
patroni /etc/patroni/patroni.yml # 前台启动(生产配 systemd)
patronictl -c /etc/patroni/patroni.yml list # 查看集群成员状态
patronictl -c /etc/patroni/patroni.yml switchover # 计划内切换
patronictl -c /etc/patroni/patroni.yml failover # 手动指定候选执行故障转移repmgr 最小结构
repmgr 的形态是「repmgr 元数据库 + repmgrd 守护进程 + 命令行工具」:
# repmgr.conf
node_id=1
node_name=node1
conninfo='host=10.0.0.10 user=repmgr dbname=repmgr connect_timeout=2'
data_directory='/var/lib/postgresql/18/main'
failover=automatic
promote_command='repmgr standby promote'
follow_command='repmgr standby follow'repmgr cluster show # 查看集群拓扑与状态
repmgr standby promote # 手工提升(repmgrd 自动提升时内部也调用它)
repmgr node rejoin --force-rewind # 旧主库带 pg_rewind 重新接入pg_auto_failover 最小结构
pg_auto_failover 引入一个独立的 monitor 节点承担仲裁职责:
# monitor 节点(独立机器或与数据库混部,生产建议独立)
pg_autoctl create monitor --pgdata /var/lib/postgresql/monitor \
--hostname monitor.internal
# 数据节点
pg_autoctl create postgres --pgdata /var/lib/postgresql/18/main \
--monitor 'postgres://auto_failover@monitor.internal/pg_auto_failover' \
--hostname node1.internal
pg_autoctl show state # 查看节点状态机
pg_autoctl perform failover # 计划内切换详细说明
四个组成部分在主流方案中的落位
| 组成 | Patroni | repmgr | pg_auto_failover | Pgpool-II |
|---|---|---|---|---|
| 健康检查 | 自身进程存活即参与 + DCS 租约续期 | repmgrd 心跳写入元库 | 节点向 monitor 上报 | health_check_period |
| 仲裁/选举 | DCS(etcd/Consul/ZK)多数派 | repmgr 元数据库投票 | monitor 节点裁决 | watchdog 多数派 |
| 提升 | 调用 pg_ctl promote | repmgr standby promote | monitor 指挥节点提升 | failover_command 脚本 |
| 流量切换 | 依赖外部(HAProxy/VIP) | 依赖外部 | monitor 提供拓扑供路由 | 内置(前端直接连 Pgpool) |
可以看到没有方案是「全栈自足」的:Patroni 专注数据层,把流量层留给 HAProxy/VIP;Pgpool-II 反过来专注流量层与连接池,数据层仲裁相对薄弱。这正是组合使用的现实原因。
手工流程与自动化流程的对应
自动方案的价值可以用一张对照表说透——手工 Runbook 里的每一步,都被自动化压缩成了什么:
| 手工步骤(Runbook) | 自动化后的形态 | 典型耗时 |
|---|---|---|
| 连续 N 次探活 + 多探针交叉验证 | 工具内置健康检查周期 + 租约机制 | 秒级 |
| 人工决策「是否值得切换」 | 判定规则固化(可保留人工确认开关) | 秒级或人工 |
| 比较各备库 replay_lsn 选候选 | 工具按内置规则自动选 | 毫秒级 |
pg_ctl promote / pg_promote() | 工具调用同一命令 | 秒级 |
| VIP / DNS / 连接池切换 | Watchdog / 路由健康检查自动完成 | 秒级 |
| 旧主库 pg_rewind 善后 | 节点重新加入时自动执行 | 与变更量相关 |
| 记录事件与时间线变化 | 事件日志 + 监控采集 | 自动 |
对照表揭示了一个重要事实:自动化没有改变任何底层动作,它改变的是「谁来执行、多快执行」。这也意味着手工流程中强调的所有检查(提升前量化丢失、善后验证),自动方案都以配置或内置逻辑的形式存在——理解手工流程依然是理解自动方案的前提。
主流方案对比
| 维度 | Patroni | repmgr | pg_auto_failover | Pgpool-II | 云托管(RDS/Aurora 等) |
|---|---|---|---|---|---|
| 架构 | DCS + 每节点 Agent | 元库 + 每节点守护进程 | monitor + 节点 | 独立代理层 | 平台托管 |
| 共识来源 | etcd/Consul/ZK 多数派 | 元数据库 | monitor 单点(可高可用) | watchdog 多数派 | 平台内部 |
| 自动故障转移 | 成熟,业界事实标准 | 成熟 | 成熟,开箱即用 | 有,但历史问题较多 | 成熟,平台负责 |
| 读写分离 | 不内置 | 不内置 | 不内置 | 内置负载均衡 | 平台提供只读端点 |
| 连接池 | 不内置 | 不内置 | 不内置 | 内置 | 平台提供 |
| pg_rewind 支持 | 自动 | node rejoin --force-rewind | 内部处理 | 有限 | 无需关心 |
| 运维复杂度 | 高(多组件) | 中 | 中低 | 中 | 低 |
| 可控性 | 完全 | 完全 | 完全 | 完全 | 低(黑盒) |
没有「最好」的方案,只有与团队运维能力、业务 SLA 匹配的方案。选型建议见文末。
共识层的作用与 DCS 不可用时的行为
共识层解决的是一个朴素的问题:当两个节点对「主库还活着吗」产生分歧时,听谁的?
没有共识层的方案,每个节点自己判定,判定结果可能相反:node1 认为主库挂了于是自己提升,主库其实只是和 node1 断了网还在正常服务——脑裂就此产生。有了 DCS 之后,判定权收归多数派:
- 主库周期性地在 DCS 中续租「领导权」;
- 候选备库只有在 DCS 中确认「领导租约已失效」且自己能获得多数派支持时,才会提升;
- 被网络分区隔离的节点拿不到多数派支持,会主动把自己降级为只读(甚至暂停),绝不多写。
DCS 不可用时的行为是理解 Patroni 的关键:
| DCS 状态 | 集群行为 |
|---|---|
| DCS 多数派存活 | 一切正常 |
| DCS 少数派节点挂掉 | 无影响 |
| DCS 整体不可用(多数派丢失) | 主库租约到期后自动降级只读,集群拒绝写入,等待 DCS 恢复 |
| DCS 恢复 | 节点重新加入,主库凭连续性恢复领导权(或重新选举) |
「DCS 挂了数据库就不让写」听起来激进,但这是正确的工程取舍:DCS 不可用意味着系统已经无法区分「谁该当主库」,此时唯一安全的选择是停止接受写入。可用性可以事后恢复,双主的数据冲突很难收拾。
DCS 的高可用就是集群的高可用
DCS 部署在单机上、或与数据库节点共享故障域(同机柜、同可用区),等于把仲裁的命脉放进了与数据库相同的故障半径。etcd 的 3 或 5 个节点应分布在不同故障域上,且数量为奇数。
脑裂、误判与 fencing
自动方案把故障判定从人转移给了软件,但软件的判定一样会错。两类最危险的情形:
误判(假故障):主库只是慢(磁盘 IO 抖动、长事务、网络抖动),健康检查超时后被判定死亡,备库提升。此时旧主库还活着,且仍然接受写入——两个写入点出现。
分区(假失联):网络分区把主库与仲裁体隔开,主库自己完全正常。与误判同样产生双主,且更难察觉(主库会向连着它的那部分客户端正常服务)。
对这两类风险,唯一的系统性解法是 fencing(隔离):提升新主库之前或同时,确保旧主库不可能再作为写入点工作。工程上按强度递增:
| fencing 手段 | 强度 | 说明 |
|---|---|---|
| 租约失效即自我降级 | 基础 | 主库失去 DCS 租约后自动转只读(Patroni 内置) |
| 提升 branch 校验 | 增强 | 提升前确认旧主库在 DCS 中的领导记录已消失 |
| 关闭数据库进程 | 强 | 对旧节点执行停止(工具通过 Agent/SSH) |
| 关闭主机电源(STONITH) | 最强 | 通过带外管理(IPMI 等)关机,防止主机本身失智 |
PostgreSQL 有一个天然的辅助机制:旧主库重接入前必须做 pg_rewind 或重建。即使短暂出现双写,善后时至少能通过时间线与 WAL 精确识别「哪边写了什么」,把冲突范围圈出来。但这只是事后审计能力,不是预防手段,不能替代 fencing。
与同步复制、复制槽的组合
自动故障转移解决「快速切换」,同步复制解决「切换时丢多少」。两者组合才有完整的 RPO 承诺:
| 组合 | 效果 | 代价 |
|---|---|---|
| 自动故障转移 + 异步复制 | RTO 小,RPO > 0(丢最近一段写入) | 无 |
自动故障转移 + 同步复制(remote_apply) | RPO = 0 | 提交延迟上升;同步备库不可用时写入阻塞 |
| 自动故障转移 + 同步复制 + 多备库 quorum | RPO = 0 且容忍单备库故障 | 架构与运维复杂度最高 |
组合时有两个工程要点:
- 提升候选必须是数据最靠前的备库。异步复制下要比较各备库的
replay_lsn;同步复制下天然只有同步备库是安全的候选。主流工具都内置了这个规则,但「最近被排除出同步名单的备库」等边角情况值得演练验证。 - 复制槽要随拓扑自动管理。节点重新加入、角色互换后,物理复制槽需要重建或迁移。手工管理槽在这种动态拓扑下容易出错(槽闲置导致
pg_wal膨胀),应依赖工具管理或配置max_slot_wal_keep_size兜底。
流量切换:VIP / HAProxy / DNS / 负载均衡
提升只解决「谁是主库」,客户端还要「找到」主库。四种主流路由方式:
| 方式 | 原理 | 切换速度 | 优缺点 |
|---|---|---|---|
| VIP(keepalived / vip-manager) | 虚拟 IP 漂移到新主库 | 秒级 | 对应用完全透明;要求同二层网络 |
| HAProxy / 负载均衡器 | 按健康检查把连接转发到当前主库 | 秒级 | 天然支持多备库读分发;多一层组件 |
| DNS | 修改域名记录指向新主库 | TTL 级(秒~分钟) | 实现简单;受客户端 DNS 缓存拖累 |
| 应用内多地址 | 应用配置多个候选,失败转移 | 取决于实现 | 无外部依赖;需要应用改造 |
HAProxy + Patroni 是当前最流行的组合,原理是利用 Patroni 的 REST API(默认 8008 端口)做健康检查:主库返回 200,备库返回 503,HAProxy 据此把写流量只发给主库:
# haproxy.cfg —— 写流量路由(读流量可类似配置多后端轮询)
listen postgres-primary
bind *:5432
mode tcp
option httpchk
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server node1 10.0.0.10:5432 check port 8008
server node2 10.0.0.11:5432 check port 8008
server node3 10.0.0.12:5432 check port 8008on-marked-down shutdown-sessions 很关键:节点被判不健康时立即切断既有连接,避免应用继续在「已失格的主库」上写。
这套路由层自身的单点问题必须解决:HAProxy 部署两台 + keepalived 管 VIP,或使用云负载均衡器。路由层挂了,数据库集群再健康也无济于事。
客户端重连策略
无论路由层多完善,切换瞬间总有一部分连接被断开。应用侧的重连行为决定了用户体验的下限:
推荐的重连策略:
1. 识别连接类错误(连接断开、无法连接、角色相关错误),与业务错误区分开
2. 指数退避 + 抖动重试(如 1s / 2s / 4s ± 随机),避免全体客户端同时重试打爆新主库
3. 有限次数(如 3~5 次)后放弃并报错,避免无限重试掩盖问题
4. 重试前重新解析路由(重新取连接池连接、刷新 DNS)
5. 写操作重试前确认幂等性:非幂等写入(INSERT 无唯一键)盲目重试可能重复数据
6. 连接池设置探测与自动重建(如 PgBouncer 的 server_lifetime / server_check_query)一个常被忽略的细节:切换后旧主库连接必须被清除。应用持有到旧主库的存量连接,即使新主库已就绪,这些连接要么报错(旧主库降级只读)要么继续在只读状态下「成功」执行只读操作——让排障者误以为服务正常。HAProxy 的 shutdown-sessions、PgBouncer 的 RECONNECT 都是为此。
监控与告警
自动故障转移方案的监控要覆盖四个层次:
| 层次 | 关键指标 | 告警建议 |
|---|---|---|
| 数据库层 | 复制延迟(replay_lag)、连接数、磁盘、WAL 堆积 | 延迟超阈值、pg_wal 水位异常 |
| 集群层 | 节点角色(谁是主)、成员数量、timeline_id | 角色意外变化、成员缺失、时间线跳变 |
| 仲裁层 | DCS 健康度、租约状态 | DCS 节点掉线、租约异常 |
| 路由层 | HAProxy 后端健康数、VIP 位置、连接成功率 | 可用后端数下降、切换事件 |
两个高价值的具体检查:
-- 集群层:从任一节点确认角色与时间线
SELECT pg_is_in_recovery(),
timeline_id
FROM pg_control_checkpoint(); pg_is_in_recovery | timeline_id
-------------------+-------------
t | 2
(1 row)-- 数据层:主库上确认备库是否在正常跟随
SELECT application_name, state, sync_state,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes
FROM pg_stat_replication; application_name | state | sync_state | lag_bytes
------------------+-----------+------------+-----------
node2 | streaming | async | 0
node3 | streaming | async | 16432
(2 rows)事件类告警(「发生过切换」)往往比指标类告警更重要:一次计划外的自动切换,即使服务无感知,也说明主库出现过健康检查失败——必须事后归因,否则同样的故障下次可能造成真实业务影响。
半自动与全自动的取舍
不是所有集群都应该全自动。按自动化程度排列:
| 模式 | 判定与提升由谁做 | RTO | 适用场景 |
|---|---|---|---|
| 纯手工 | 人 | 十分钟~小时级 | 开发测试、数据一致性要求极端严格 |
| 半自动(人判定,工具执行) | 人判定故障,一条命令完成提升+切换 | 分钟级 | 有 DBA 值班、可接受分钟级中断 |
| 全自动(failover) | 软件判定并提升 | 秒级~1 分钟 | 7×24 业务、无法容忍分钟级中断 |
| 全自动(托管) | 云平台 | 平台承诺值 | 团队无数据库运维能力 |
半自动模式(例如 Patroni 的 failover 手动触发、repmgr 的 --manual 模式、pg_auto_failover 的 PGOPTIONS 控制)常被低估:它保留了人对「是否值得切换」的判断(比如主库只是短暂抖动,等 30 秒可能比切换更划算),同时用工具消除执行环节的手误。对多数中小团队,半自动是投入产出比最好的起点,全自动可以在半自动流程跑熟之后逐步开启。
演练与爆炸半径控制
自动故障转移的可靠性只能靠演练证明。演练设计有两个要点:
演练频率与场景覆盖:至少覆盖「干净故障」(停主库进程)、「脏故障」(kill -9)、网络分区(iptables 断流)三类,分别验证判定与 fencing 的表现。
爆炸半径控制:演练与真实故障时都要限制影响面:
- 单集群演练:不要同时对多个生产集群做演练;
- 可快速回退:演练前确认「切回原主库」的路径可用(切换是双向可逆的,故障转移往往不是);
- 错峰:避开业务高峰与大事务窗口;
- 灰度流量:条件允许时,演练期间把部分只读流量摘除,降低切换抖动的影响面;
- 事件留痕:每次演练记录触发方式、判定耗时、提升耗时、流量切换耗时、应用感知时间,形成 RTO 的真实台账。
云托管方案的能力与限制
RDS、Aurora、Cloud SQL 等托管数据库提供开箱即用的高可用,但能力边界与自建不同:
| 能力 | 表现 |
|---|---|
| 故障转移 | 平台负责,通常秒级到分钟级,应用重连即可 |
| 可观测性 | 平台指标丰富,但看不到操作系统与进程内部 |
| 主机访问 | 通常无法 SSH 登录实例主机 |
| 工具兼容性 | 部分 local 工具(pg_rewind、pg_controldata、部分扩展)不可用或受限 |
| 参数自由度 | 部分参数不可修改(重启相关、文件系统相关) |
| 复制槽 | 提供度不一,部分平台限制物理复制槽的使用 |
对团队的真正含义是:自建集群的很多排障手段在托管环境里失效。例如无法在故障现场用 pg_controldata 检查控制文件,无法用 pg_rewind 手工回收旧主库(平台自己处理)。选择托管意味着接受「更少的控制权换更少的运维负担」,这个交换是否划算取决于业务对可控性的要求。
选型建议
| 场景 | 推荐 |
|---|---|
| 有专职 DBA、要求完全可控 | Patroni + etcd + HAProxy/VIP |
| 团队较小、想要相对简单的自建 | pg_auto_failover 或 repmgr |
| 已大量使用 PostgreSQL 生态、还要连接池与读写分离 | Pgpool-II(或 Pgpool-II + Patroni 组合,注意职责划分) |
| 无专职运维、可接受平台约束 | 云托管高可用版 |
| 强一致要求(RPO=0 硬指标) | Patroni + 同步复制(remote_apply)+ quorum 多备库 |
组合使用的提醒:Patroni 与 Pgpool-II 的组合中,两者职责必须划清——Patroni 管数据层的选主与提升,Pgpool-II 只做连接路由与负载均衡,不要让 Pgpool-II 的 failover 机制与 Patroni 的选主同时生效,否则两套「主库判定」互相打架。
实战示例
示例一:部署前的基础设施核对
在部署任何自动故障转移方案之前,逐项核对清单:
# 1. 各节点 PostgreSQL 版本一致
psql -h 10.0.0.10 -c "SHOW server_version;"
psql -h 10.0.0.11 -c "SHOW server_version;"
# 2. pg_rewind 前提已具备(自动方案善后依赖它)
psql -h 10.0.0.10 -c "SHOW wal_log_hints;"
# 3. 节点间网络互通(包括 DCS 端口)
for host in 10.0.0.10 10.0.0.11 10.0.0.12; do
echo -n "$host pg: "; pg_isready -h $host -p 5432 -t 2
echo -n "$host etcd: "; timeout 2 bash -c "echo > /dev/tcp/$host/2379" \
&& echo "open" || echo "closed"
done10.0.0.10 pg: 10.0.0.10:5432 - accepting connections
10.0.0.11 pg: 10.0.0.11:5432 - accepting connections
10.0.0.10 etcd: open
10.0.0.11 etcd: open示例二:用 Patroni 完成一次演练故障转移
# 1. 观察当前集群状态
patronictl -c /etc/patroni/patroni.yml list+ Cluster: pg-cluster -----+---------+---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+-----------+---------+---------+----+-----------+
| node1 | 10.0.0.10 | Leader | running | 1 | |
| node2 | 10.0.0.11 | Replica | running | 1 | 0 |
| node3 | 10.0.0.12 | Replica | running | 1 | 0 |
+--------+-----------+---------+---------+----+-----------+# 2. 模拟主库故障(演练环境)
ssh 10.0.0.10 "sudo systemctl stop patroni"
# 3. 等待自动提升完成后查看状态
patronictl -c /etc/patroni/patroni.yml list+ Cluster: pg-cluster -----+---------+---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+-----------+---------+---------+----+-----------+
| node1 | 10.0.0.10 | Replica | running | 2 | 0 |
| node2 | 10.0.0.11 | Leader | running | 2 | |
| node3 | 10.0.0.12 | Replica | running | 2 | 0 |
+--------+-----------+---------+---------+----+-----------+解读:node2 被自动提升,时间线 1 → 2;node1 恢复 Patroni 后没有手工干预就自动以备库身份回归(TL 列同为 2)——这正是 Patroni 内部调用 pg_rewind 完成善后的表现。记录从停止到提升完成的耗时,就是这套方案的实测 RTO。
示例三:验证 DCS 不可用时的自我保护
# 演练环境:停掉全部 etcd(模拟 DCS 整体不可用)
ssh etcd1 "sudo systemctl stop etcd"
ssh etcd2 "sudo systemctl stop etcd"
ssh etcd3 "sudo systemctl stop etcd"
# 等待主库租约到期后,验证写入被拒绝
psql -h 10.0.0.11 -c "CREATE TABLE dcs_down_test(id int);"ERROR: cannot execute CREATE TABLE in a read-only transaction# 恢复 etcd,观察集群回到可写状态
for h in etcd1 etcd2 etcd3; do ssh $h "sudo systemctl start etcd"; done解读:主库在失去仲裁后自动降级只读,宁可不可写也不产生无主状态。这是验证 fencing 生效的最直接方法。演练后记得清理测试表。
示例四:HAProxy 路由层验证
# 模拟应用视角:写流量应始终落到当前主库
psql -h haproxy.internal -p 5432 -c \
"SELECT inet_server_addr(), pg_is_in_recovery();" inet_server_addr | pg_is_in_recovery
------------------+-------------------
10.0.0.11 | f
(1 row)# 触发一次切换,再查一次(应自动指向新主库)
patronictl -c /etc/patroni/patroni.yml switchover --leader node2 --candidate node3
sleep 10
psql -h haproxy.internal -p 5432 -c \
"SELECT inet_server_addr(), pg_is_in_recovery();" inet_server_addr | pg_is_in_recovery
------------------+-------------------
10.0.0.12 | f
(1 row)应用侧无任何配置变更,写连接自动落到新主库——这就是路由层与仲裁层协同工作的证明。
示例五:切换事件的监控留痕
-- 巡检脚本片段:核对角色、时间线、复制健康
SELECT current_setting('cluster_name') AS cluster,
pg_is_in_recovery() AS is_standby,
(SELECT timeline_id FROM pg_control_checkpoint()) AS tl,
(SELECT count(*) FROM pg_stat_replication) AS standby_count; cluster | is_standby | tl | standby_count
------------+------------+----+---------------
pg-cluster | f | 2 | 2
(1 row)把这个查询接入监控采集,tl 字段的变化即为提升事件信号,standby_count 下降即为降级告警信号。
示例六:RTO 台账模板
演练与真实事件的耗时数据是评估「自动故障转移是否达标」的唯一依据。用一个固定格式的台账积累:
┌─────────────────────────────────────────────────────────────┐
│ 事件台账 #2026-09-30-01(演练:干净停库) │
├─────────────────────────────────────────────────────────────┤
│ 触发方式 systemctl stop patroni(主库 node1) │
│ 判定耗时 故障发生 → 仲裁确认失格 = 14s │
│ 提升耗时 失格确认 → 新主可写 = 6s │
│ 路由耗时 新主可写 → 应用获得可写连接 = 9s │
│ 应用感知 第一条成功写请求 = 提升后 12s │
│ 善后耗时 node1 以备库身份回归 = 45s(pg_rewind 自动) │
│ 总 RTO ≈ 29s(判定+提升+路由) │
│ 遗留事项 应用侧两条连接重试告警,需核对退避配置 │
└─────────────────────────────────────────────────────────────┘台账要固定字段、每次演练必填,季度汇总时就能回答两个问题:RTO 是否稳定达标、哪个环节的耗时在恶化。多数团队的「自动故障转移达不到 SLA」,拆到台账层面都能定位到单一环节(通常是路由或善后)。
注意事项
自动故障转移不能修复「主库挂了但 WAL 没到备库」造成的数据丢失。 异步复制下自动切换照旧丢数据,对 RPO 有硬要求的业务必须叠加同步复制。
不要关闭工具的 fencing / 租约机制来「减少麻烦」。 DCS 不可用时拒绝写入是特性不是缺陷;绕过它的每一次「优化」都在给脑裂开绿灯。
自动提升的候选选择规则要事前确认。 默认规则(数据最靠前)未必符合业务优先级(例如希望同可用区优先),主流工具都支持配置优先级与候选排除。
路由层自身的单点必须消除。 单台 HAProxy、单个 keepalived 节点都会成为新瓶颈;路由层的高可用与数据库层同等重要。
旧主库重接入依赖 pg_rewind 前提。 wal_log_hints 或数据校验和必须提前开启,否则自动善后会退化为全量重建,RTO 被显著拉长。
应用重连与幂等重试是切换体验的另一半。 数据库侧做得再好,应用不重连或盲目重试非幂等写入,切换照样出事故。
复制槽要随动态拓扑管理。 节点角色频繁互换的环境里,手工管理复制槽容易产生闲置槽撑爆 pg_wal;用工具管理并配置 max_slot_wal_keep_size 兜底。
演练要覆盖「脏故障」与网络分区。 只演练干净停库验证不了误判防护;不演练分区验证不了 fencing。
时间线跳变要接入告警。 自动提升会递增 timeline_id,监控它比监控进程存活更能反映「集群发生过什么」。
不要在没跑熟半自动前直接上全自动。 把提升前检查、善后流程在半自动模式下演练充分,再放开全自动,出问题时团队的肌肉记忆才够用。
托管数据库要重新评估运维假设。 主机不可登录、部分工具不可用,自建时代的排障手册需要针对性改写。
事件台账是演练的最终产出物。 只演练不记录,等于验证完即遗忘;判定、提升、路由、善后各环节的耗时趋势,是判断集群健康度的长期依据。
最佳实践
按「健康检查 + 仲裁 + 提升 + 流量切换」四件套评估任何方案。 缺任何一件的方案都要想清楚补位手段,否则就是裸奔。
DCS(etcd 等)至少 3 节点、分布在不同故障域、奇数个。 DCS 的可用性与故障域隔离程度直接决定整个集群的可用性。
wal_log_hints作为自动方案集群的强制基线配置。 自动善后依赖 pg_rewind,pg_rewind 依赖 hints 或校验和,这条依赖链要在部署阶段闭合。用 HAProxy + Patroni REST API 的组合做写流量路由。 健康检查直接反映主库资格,配置简单且行为可预期;务必保留
on-marked-down shutdown-sessions。同步复制与自动故障转移按业务要求组合。 RPO=0 用
remote_apply;接受少量丢失用异步但要监控复制延迟上限。客户端实现指数退避 + 有限次数 + 幂等判断的重连策略。 把重连策略写进应用的错误处理规范,而不是各服务自由发挥。
对「切换事件」与「时间线变化」设置独立告警。 指标超标告警之外,事件类告警是发现静默故障(如周期性抖动导致的反复切换)的唯一手段。
每季度演练三类故障:干净停库、进程强杀、网络分区。 记录每次的判定耗时、提升耗时、流量切换耗时与业务感知时间,形成 RTO 台账。
从半自动模式起步,流程跑熟后开启全自动。 团队对提升前检查与善后流程建立了肌肉记忆,全自动才有可靠的行为边界。
爆炸半径控制写进演练与应急流程。 单集群演练、可回退路径预确认、错峰执行、流量灰度,四条都是低成本高收益的约束。
选择托管方案时逐项确认能力边界。 主机访问、工具可用性、参数自由度、复制槽支持,都在采购阶段确认,不要等故障时才发现手段失效。
文档化「谁有资格执行提升」。 即使全自动,也要写清手动干预的入口(patronictl failover、repmgr standby promote)、审批流程与操作步骤,避免事故中临时找权限。
下一节
继续学习:PgBouncer 连接池