Patroni 高可用方案
Patroni 是目前 PostgreSQL 生态中事实标准的高可用编排方案。它用 Python 编写,以守护进程的形式与 PostgreSQL 同节点部署,借助一个分布式配置存储(DCS,Distributed Configuration Store)完成领导选举、自动故障转移、备库克隆与配置下发。应用与运维人员面对的不再是一堆散落的 pg_ctl、pg_rewind、postgresql.conf 手工操作,而是一个会自我修复的集群:主库挂了,它在数秒到数十秒内选出并提升新主库;旧主库回来后,它自动降级、回卷、重新以备库身份加入。
Patroni 的设计哲学是「把决策权交给共识系统」。谁是主库这件事不再由任何单点进程或人工判断决定,而是由 DCS 里的 leader key 租约决定:持有租约的节点是主库,租约过期即失效,存活的节点竞争接替。这个模型天然规避了「两个节点都认为自己是主库」的经典脑裂问题,也让集群的全局配置(同步模式、复制参数)有了唯一可信的存放处。
本页是「连接池与高可用方案」小节的核心:前两页的 PgBouncer 与 HAProxy 回答了「连接发给谁」,本页的 Patroni 回答「谁有资格当主库」。内容覆盖架构组成、patroni.yml 配置结构、故障转移流程、同步模式、REST API、patronictl 运维命令、pg_rewind 集成与脑裂防护,最后给出部署方式与监控要点。
前置知识
建议先阅读:HAProxy / Keepalived
基础概念
| 概念 | 英文 / 记法 | 说明 |
|---|---|---|
| DCS | Distributed Configuration Store | 分布式配置存储,leader key 所在,集群状态的唯一权威 |
| leader key | leader key | DCS 中的键,持有其租约的节点即为主库 |
| 租约 / TTL | lease / ttl | leader key 的有效期,主库须持续续期,过期即视为失效 |
| 选举 | election | leader key 过期后,存活节点竞争接替的过程 |
| 工作循环 | loop | Patroni 每 loop_wait 秒执行一轮:健康检查、续租、对齐配置 |
| REST API | restapi | Patroni 的 HTTP 接口(默认 8008),健康检查与运维入口 |
| patronictl | patronictl | Patroni 的命令行管理工具 |
| 动态配置 | dynamic configuration | 存放在 DCS 中、全集群共享的 PostgreSQL 参数集 |
| 回卷 | pg_rewind | 把脱离主线的旧主库时间线回卷对齐到新主库的工具 |
| 隔离 | fencing | 确保旧主库不再接收写入的机制(DCS 仲裁 + watchdog) |
| 脑裂 | split-brain | 两个节点同时对外充当主库的危险状态 |
支持的 DCS 后端
Patroni 支持四类 DCS,能力等价( leader 选举、TTL 租约、watch 通知),按现有基础设施选择即可:
| DCS | 典型部署 | 特点 |
|---|---|---|
etcd(v3 API,etcd3) | 3 或 5 节点独立集群 | Patroni 官方最常用的组合,轻量易运维 |
| Consul | 已有 HashiCorp 体系 | 会话与服务发现一体,运维门槛略高 |
| ZooKeeper | 已有 Kafka/Hadoop 体系 | 成熟稳定,但运维负担最重 |
| Kubernetes API | 容器平台 | 用 K8s API 自身当 DCS,配合 Operator 是云原生标准形态 |
DCS 自身必须高可用(奇数节点,3 或 5 台),它是整个集群的大脑——DCS 不可用不等于数据库不可写(现有主库继续服务),但无法完成故障转移,也拿不到选举结果,这在容量规划里要当成独立的可用性依赖来对待。
语法与用法
Patroni 的全部静态信息都在每个节点的 patroni.yml 里;集群级动态配置存放在 DCS,由 patronictl edit-config 修改。一个三节点集群中 node1 的完整示例:
scope: pg-prod
namespace: /db/
name: node1
restapi:
listen: 0.0.0.0:8008
connect_address: 192.168.1.11:8008
etcd3:
hosts: 192.168.1.21:2379,192.168.1.22:2379,192.168.1.23:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
wal_log_hints: "on"
max_wal_senders: 10
max_replication_slots: 10
pg_hba:
- host replication replicator 192.168.1.0/24 scram-sha-256
- host all all 192.168.1.0/24 scram-sha-256
initdb:
- encoding: UTF8
- data-checksums
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.11:5432
data_dir: /var/lib/postgresql/18/pg-prod
bin_dir: /usr/lib/postgresql/18/bin
parameters:
unix_socket_directories: /var/run/postgresql
authentication:
superuser:
username: postgres
password: Ch4ngeM3_Str0ng
replication:
username: replicator
password: R3pl_Secret
rewind:
username: rewind
password: R3w1nd_Secret
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false启动 Patroni 与首次初始化:
# 首个节点:bootstrap 会执行 initdb 并写入 DCS 初始配置
patroni /etc/patroni.yml
# 其余节点:直接启动,Patroni 自动从主库 pg_basebackup 克隆数据目录
patroni /etc/patroni.ymlpatronictl 是日常运维的主通道(-d 指定 DCS 或依赖本地配置发现集群):
| 命令 | 作用 |
|---|---|
patronictl list | 查看集群成员、角色、延迟与状态 |
patronictl show-config | 查看 DCS 中的动态配置 |
patronictl edit-config | 编辑动态配置,保存后全集群滚动生效 |
patronictl switchover | 计划内主备切换(旧主库在线) |
patronictl failover | 紧急主备切换(旧主库不可用时使用) |
patronictl restart | 重启某节点(可定时、可指定候选) |
patronictl reinit | 重建某备库的数据目录 |
patronictl reload | 让节点重载配置 |
patronictl pause / resume | 进入/退出维护模式(暂停自动故障转移) |
patronictl history | 查看故障转移历史 |
patronictl dsn | 输出节点连接串 |
patronictl remove | 彻底移除集群在 DCS 中的信息(高危) |
patronictl -d /etc/patroni.yml list+ Cluster: pg-prod -----+---------+---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+---------------+---------+---------+----+-----------+
| node1 | 192.168.1.11 | Leader | running | 2 | |
| node2 | 192.168.1.12 | Replica | running | 2 | 0 |
| node3 | 192.168.1.13 | Replica | running | 2 | 0 |
+--------+---------------+---------+---------+----+-----------+详细说明
架构组成与工作循环
每个节点上有两个常驻进程:Patroni 守护进程与它监管的 PostgreSQL 实例。Patroni 本身由四部分组成:监管器(拉起/停止 PostgreSQL、执行切换动作)、健康检查与选举逻辑(读写 DCS)、REST API(对外暴露状态与操作入口)、配置管理(把 DCS 中的动态配置下发到 postgresql.conf 并触发 reload/restart)。
Patroni 每隔 loop_wait 秒执行一轮工作循环:
┌─────────────── Patroni 工作循环(每 loop_wait 秒)───────────────┐
│ │
│ 1. 检查本机 PostgreSQL 健康(可连接?角色正确?) │
│ 2. 与 DCS 交互:我是 leader?→ 续期 leader key │
│ 我是 replica?→ 观察 leader key 是否仍在租约内 │
│ 3. 对齐动态配置:DCS 中的 parameters / pg_hba 有变化则下发 │
│ 4. 维护复制槽、执行异步任务(克隆、rewind、清理等) │
│ 5. 睡眠至下一轮 │
└──────────────────────────────────────────────────────────────────┘这个循环解释了两个经常被问到的现象:其一,Patroni 是定期与 DCS 通信的,故障发现时间 = ttl 租约过期等待 + 选举与提升耗时,而不是毫秒级;其二,所有角色相关的判断都以 DCS 为准,Patroni 从不「自作主张」宣布自己是主库——没有租约就没有写入资格。
DCS 与 Leader 选举
DCS 中与本集群相关的键都挂在 namespace/scope 前缀下,核心是 leader 键:
/db/pg-prod/leader → "node1" 谁持有它,谁是主库
/db/pg-prod/members/node1 → 节点元数据(地址、角色、时间线等)
/db/pg-prod/config → 动态配置(bootstrap.dcs 的运行时形态)
/db/pg-prod/failover → 人工触发的切换请求
/db/pg-prod/history → 故障转移历史(时间线分叉点)主库节点每轮循环(不超过 retry_timeout)为 leader 键续期;租约总时长是 ttl 秒。选举过程:
- 主库进程崩溃或与 DCS 失联,
leader键在ttl秒后过期; - 各备库节点在下一轮循环发现 leader key 已过期,进入候选状态;
- 候选者自检:本机数据足够新(落后不超过
maximum_lag_on_failover字节)、时间线可参与竞争; - 竞争获得 leader key 的节点执行提升(
pg_promote),更新 DCS 中的成员信息; - 其余节点观察到新主库,重新指向它继续流复制。
DCS 不可用 ≠ 数据库不可用
DCS 全挂时,在位主库因无法续租不会主动降级(默认行为下它会继续运行),集群正常读写;但此时发生主库故障将无法自动转移,新节点也无法加入。因此 DCS 的可用性监控与数据库同等重要。
DCS 后端在配置文件里按 API 版本区分:v3 接口用 etcd3: 键,v2 接口用 etcd: 键,两者是不同的配置段且不可混用。PostgreSQL 生态当前的主流选择是 etcd3(v2 API 在新版 etcd 中已移除),Consul 对应 consul: 键、ZooKeeper 对应 zookeeper: 键。键名写错时 Patroni 启动即报「no DCS is available」,这是初次部署最常见的第一道坎。
patroni.yml 配置结构详解
patroni.yml 的顶层键分四组,含义与作用域各不相同:
| 顶层键 | 作用 | 存放位置 |
|---|---|---|
scope / namespace / name | 集群名、DCS 键前缀、本节点唯一名 | 本地文件 |
restapi | REST API 监听地址与端口(默认 8008)、TLS 等 | 本地文件 |
etcd3 / consul / zookeeper | 所用 DCS 的连接信息 | 本地文件 |
bootstrap | 首次初始化集群时的初始 DCS 配置与 initdb 参数 | 只在 bootstrap 时写入 DCS |
postgresql | 本节点的 PostgreSQL 连接与运行信息 | 本地文件(动态部分在 DCS) |
postgresql 段是本地节点的核心:listen(对外监听)、connect_address(告知其他节点的连接地址)、data_dir、bin_dir、parameters(本节点参数)、pg_hba(本节点 HBA)、authentication(superuser / replication / rewind 三组凭据)与 tags。其中三个认证角色各有用途:replication 用于流复制与备库克隆,rewind 用于 pg_rewind(需授予相应函数执行权限),superuser 用于管理操作——三组账号分开,避免权限过度集中。
bootstrap.dcs.postgresql.parameters 中的参数会被写入 DCS 成为动态配置,此后以 patronictl edit-config 为准;本地 postgresql.parameters 只覆盖本节点(如 unix_socket_directories)。两类参数冲突时以动态配置优先,这是「全局统一、本地微调」的分层模型。
tags 控制单个节点在集群中的参与度:nofailover: true 使其永不参与提升(适合异地备库)、noloadbalance: true 让 HAProxy 类组件读路由跳过它、clonefrom: true 标记其为优先克隆源、nosync: true 将其排除在同步复制候选之外。
bootstrap.dcs 全局参数
bootstrap.dcs 里的参数决定集群的「心跳节奏」与故障转移边界,多数可在运行时经 edit-config 调整:
| 参数 | 默认值 | 说明 |
|---|---|---|
ttl | 30 | leader key 租约时长(秒),决定故障发现的下限 |
loop_wait | 10 | 工作循环间隔(秒),绝大多数操作的最小粒度 |
retry_timeout | 10 | 与 DCS 交互及复制连接的超时(秒) |
maximum_lag_on_failover | 1048576 | 参与提升的备库允许的最大落后字节数(默认 1MB) |
master_start_timeout | 300 | 主库启动等待超时(秒),超时则放弃等待转入转移 |
synchronous_mode | false | 是否开启同步复制模式 |
synchronous_mode_strict | false | 严格同步:无可用同步备库时阻止提交 |
synchronous_node_count | 1 | 同步备库数量 |
standby_cluster | 无 | 将整组备库组织为级联的 standby cluster(异地容灾) |
postgresql.use_pg_rewind | false | 旧主库重新加入时是否允许 pg_rewind 回卷 |
postgresql.use_slots | true | 是否由 Patroni 管理物理复制槽 |
三者的关系值得记住:ttl ≥ loop_wait + retry_timeout,故障发现时间约等于 ttl,整体 RTO 约 ttl + 提升 + 应用重连,把 ttl 从 30 调到 15 可以显著缩短转移窗口,但也会放大网络抖动引起的误切换,需结合机房网络质量决定。
故障转移流程详解
把一次完整的主库故障转移按时间轴拆开:
T0 主库 node1 进程崩溃 / 主机断电
T0+~ttl DCS 中 leader key 租约过期(ttl=30 时最长等 30 秒)
T0+ttl node2 / node3 下一轮循环发现 leader 缺失,进入候选
- 自检复制延迟 ≤ maximum_lag_on_failover
- 比较时间线与 latest LSN,选出数据最新的候选
T1 node2 竞争获得 leader key,执行 pg_promote 提升,时间线 +1
T1+ node3 发现新主库,断开与 node1 的复制,改指 node2
T2 应用经 HAProxy 感知角色变化,重连到新主库
T3 node1 恢复上线 → 发现自己已非 leader → 降级为备库
→ 若数据目录领先于新主库分叉点,pg_rewind 回卷后重新加入几个关键细节:
- 提升由
pg_promote()完成(PostgreSQL 12+),比pg_ctl promote少一次进程间握手,失败也能被 Patroni 直接感知。 - 复制槽:Patroni 为每个备库维护物理复制槽,故障转移后新主库会自动重建各备库的槽,避免切换期间 WAL 回收。复制槽机制参见复制槽。
standby_cluster:把一组远端备库组织成「挂在主集群后面的从集群」,异地容灾时主集群完成转移后,standby cluster 会自动跟随新主库,无需人工干预。
把各阶段的耗时来源与可控参数放在一起,便于做 RTO 预算:
| 阶段 | 耗时来源 | 主要可控参数 |
|---|---|---|
| 故障发现 | leader key 租约等待 | ttl |
| 选举与自检 | 下一轮工作循环 + 延迟校验 | loop_wait、maximum_lag_on_failover |
| 提升执行 | pg_promote() 完成提升 | ——(本身很快,秒级以内) |
| 其余备库跟随 | 重连新主库、重建复制槽 | retry_timeout |
| 上游流量恢复 | HAProxy 检查翻转 + 应用重连 | HAProxy 侧 inter/fall/rise |
以默认参数估算:ttl=30 时故障发现最多 30 秒,加上一个 loop_wait 周期与提升、跟随,整体 RTO 约在 40~50 秒量级;把 ttl 调到 15、loop_wait 调到 5,可以把 RTO 压到 20 秒上下——前提是网络足够稳定,误切换风险可接受。
同步模式(synchronous_mode)
默认异步复制下,故障转移可能丢失最后若干事务。开启 synchronous_mode: true 后,Patroni 接管 synchronous_standby_names:始终有且仅有 synchronous_node_count 个备库被标为同步候选,主库的提交会等待同步备库确认(配合 synchronous_commit 的等级语义,见synchronous_commit 配置)。
Patroni 在此之上的增值是转移时的同步语义一致性:提升候选会优先从同步备库中选取(数据不落后),确保 RPO=0 的承诺在切换后仍然成立。synchronous_mode_strict: true 进一步收紧:当没有任何可用同步备库时,主库直接阻止事务提交(宁可不可写,不可丢数据)——这是金融类场景的取舍,代价是一次网络抖动就可能造成写入停摆,普通业务建议保持 strict 关闭。
standby_names 的选择语法(FIRST / ANY、quorum 语义)与命名规则本身属于 PostgreSQL 能力,参见synchronous_standby_names。
在动态配置中开启同步模式只需三行,保存后 Patroni 会立即改写各节点的 synchronous_standby_names:
synchronous_mode: true
synchronous_mode_strict: false
synchronous_node_count: 1开启后的验证方法:patronictl list 中应有恰好一个备库标记为同步候选;主库上查询 SHOW synchronous_standby_names; 能看到 Patroni 写入的名字;pg_stat_replication 的 sync_state 列出现 sync。两个易踩的坑:其一,synchronous_mode 与 synchronous_node_count 大于当前备库数时主库会阻塞提交(strict 关闭时表现为等待,等待超时行为取决于 synchronous_commit 等级);其二,同步模式与「主库磁盘故障」叠加时,未送达同步备库的事务会随主库一起丢失,这与普通同步复制的语义一致,不是 Patroni 的额外缺陷。
REST API 端点详解
REST API 既是 HAProxy 的健康检查数据源,也是自动化运维的编程接口:
| 端点 | 方法 | 语义 |
|---|---|---|
/ | GET | 本节点状态 JSON(state、role、时间线等) |
/primary(/master) | GET | 是主库返回 200,否则 503 |
/replica | GET | 是备库返回 200(可带 lag 参数校验延迟) |
/sync / /async | GET | 是同步/异步备库返回 200 |
/read-only | GET | 可提供只读服务返回 200 |
/health | GET | 节点健康返回 200(不区分角色) |
/leader | GET | 是主库(或 standby cluster 主库)返回 200 |
/switchover / /failover | POST | 发起计划内/紧急切换(JSON 指定 leader 与 candidate) |
/config | GET / PATCH | 读取/修改动态配置 |
/history | GET | 故障转移历史 |
/metrics | GET | Prometheus 格式指标 |
/liveness / /readiness | GET | Kubernetes 探针 |
# 健康检查三连:分别用于写路由、读路由与存活探测
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.1.11:8008/primary
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.1.12:8008/replica
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.1.11:8008/health
# 以 API 方式发起计划内切换(等价于 patronictl switchover)
curl -s -X POST http://192.168.1.11:8008/switchover \
-H 'Content-Type: application/json' \
-d '{"leader": "node1", "candidate": "node2"}'
# 抓取 Prometheus 指标
curl -s http://192.168.1.11:8008/metrics | head -n 5200
503
200pg_rewind 集成、脑裂防护与 watchdog
pg_rewind 集成。故障转移后旧主库重新上线时,它的数据目录可能与新主库存在时间线分叉(多写了几个事务)。use_pg_rewind: true 让 Patroni 自动用 pg_rewind 把旧主库回卷到分叉点再重新加入,替代了「扔掉数据目录重新做基础备份」的粗放做法。启用前提有两个,缺一不可:wal_log_hints 设为 on(或建库时启用数据页校验和),以及一个具备相应权限的 rewind 认证账号。pg_rewind 的完整用法以 PostgreSQL 官方文档为准(见文末参考链接)。
脑裂与 fencing。Patroni 的第一道防线是 DCS 仲裁:没有租约的节点不会把自己提升为主库,已降级的节点写操作会被拒绝,这从机制上排除了「双主」的大多数情形。但有一类残余风险:主库与 DCS 之间网络分区时,它知道自己可能已丢租约,但无法确定是否真的过期——若此时它继续服务,与新主库形成短暂双写窗口。watchdog 就是针对这种情形的最后防线:
watchdog:
mode: required # required: 拿不到 watchdog 就拒绝当主库
device: /dev/watchdog
safety_margin: 5其原理是内核看门狗(如 softdog 模块):Patroni 持续喂狗,一旦自身失去租约又无法确认状态,就停止喂狗,内核在超时后强制重启机器——用「硬重启」保证旧主库不可能继续写入。mode: required 是对数据一致性要求最高的形态;automatic 则在设备缺失时降级为不启用。
生产环境建议启用 watchdog
仅靠 DCS 租约的 fencing,在「主库与 DCS 网络分区但主库与业务网络正常」的分区形态下存在残余双写风险。金融级要求下,配置 watchdog(mode: required)+ 同步模式,才是完整的零数据丢失方案。
与 HAProxy / VIP 的配合
Patroni 不直接提供对外虚拟 IP,路由交给 HAProxy(推荐)或 Keepalived:
- HAProxy 模式:
option httpchk GET /primary探测各节点 8008,写端口流量自动跟随真主库(见上一页)。这是最灵活的形态,天然支持读写分离多端口。 - Keepalived VIP 模式:VIP 直接绑定主库所在节点。早期版本可通过
postgresql.callbacks的on_role_change钩子迁移 VIP,官方已将其标注为弃用;现行的社区实践多以 HAProxy 层替代,或用独立脚本监听 DCS 变化迁移 VIP(需自行保证幂等,以官方文档为准)。 - 备库路由:HAProxy 读端口探测
/replica,把只读流量摊到备库;也可结合/sync、/async端点构建延迟敏感的分层读池。
两种形态下,应用与 PgBouncer 都不需要感知任何节点地址——它们只认 HAProxy 端口或 VIP,故障转移对上游完全透明。
部署方式与监控
裸机 / 虚机(systemd):pip install "patroni[etcd3]" 安装,手写 systemd 单元托管:
[Unit]
Description=Patroni HA node
After=network.target
[Service]
Type=simple
User=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni.yml
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.targetDocker:官方仓库提供镜像与 compose 示例;生产级容器镜像常用 Zalando 的 Spilo(Patroni + PostgreSQL + 备份工具的封装)。
Kubernetes:Zalando postgres-operator 基于 Spilo + Patroni,用 K8s API 充当 DCS,是云原生环境的标准形态;/liveness、/readiness 探针即为 K8s 设计。
监控要点:
| 监控对象 | 来源 | 关注点 |
|---|---|---|
| 集群成员与角色 | patronictl list / REST / | 角色漂移、State 非 running、时间线分叉 |
| 复制延迟 | REST /replica?lag= 或 pg_stat_replication | 超过 maximum_lag_on_failover 会失去提升资格 |
| DCS 健康 | etcd/Consul 自身指标 | 3/5 节点仲裁是否完整 |
| Patroni 进程 | systemd / /liveness | 进程退出即失去监管能力 |
| 动态配置变更 | patronictl history / DCS watch | 计划外变更告警 |
实战示例
示例一:从零搭建三节点集群
# 1. DCS:三台机器安装并启动 etcd(集群模式,配置略)
# 2. 各节点安装 Patroni 与 PostgreSQL 18
pip install "patroni[etcd3]"
# 3. 准备 /etc/patroni.yml(见「语法与用法」完整示例,仅 name/地址不同)
# 4. 在 node1 上首次启动:自动 initdb 并写入初始 DCS 配置
sudo systemctl start patroni
# 5. 在 node2 / node3 上启动:自动从主库克隆数据目录并加入
sudo systemctl start patroni
# 6. 确认集群形态
patronictl -d /etc/patroni.yml list+ Cluster: pg-prod -----+---------+---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+---------------+---------+---------+----+-----------+
| node1 | 192.168.1.11 | Leader | running | 1 | |
| node2 | 192.168.1.12 | Replica | streaming | 1 | 0 |
| node3 | 192.168.1.13 | Replica | streaming | 1 | 0 |
+--------+---------------+---------+---------+----+-----------+示例二:计划内切换(switchover)
# 交互式切换,指定候选节点;旧主库自动降级并跟随
patronictl -d /etc/patroni.yml switchover \
--leader node1 --candidate node2 --force
# 也支持定时切换(业务低峰执行)
patronictl -d /etc/patroni.yml switchover \
--leader node1 --candidate node2 --scheduled '2026-10-08T02:00+08' --forceCurrent cluster topology
+ Cluster: pg-prod -----+---------+---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+---------------+---------+---------+----+-----------+
| node1 | 192.168.1.11 | Replica | running | 2 | 0 |
| node2 | 192.168.1.12 | Leader | running | 2 | |
| node3 | 192.168.1.13 | Replica | running | 2 | 0 |
+--------+---------------+---------+---------+----+-----------+计划内切换走 switchover,旧主库会先被优雅降级(等事务排空)再提升候选,全程无数据丢失;只有旧主库已经不可用时才用 failover。
示例三:模拟主库故障,观察自动转移
# 直接杀掉主库节点的 PostgreSQL(模拟崩溃)
sudo pkill -9 -f 'postgres -D'
# 在另一节点上观察转移过程(约 ttl + 提升耗时)
watch -n 2 'patronictl -d /etc/patroni.yml list'+ Cluster: pg-prod -----+---------+---------+----+-----------+
| Member | Host | Role | State | TL | Lag in MB |
+--------+---------------+---------+---------+----+-----------+
| node1 | 192.168.1.11 | Replica | running | 2 | 0 |
| node2 | 192.168.1.12 | Leader | running | 2 | |
| node3 | 192.168.1.13 | Replica | running | 2 | 0 |
+--------+---------------+---------+---------+----+-----------+旧主库 node1 被 systemd/Patroni 重新拉起后发现租约易主,自动降级为备库并跟随 node2——整个回路无需人工介入。用 patronictl history 可以查到这次转移的时间线记录。
示例四:修改动态配置(全集群生效)
# 交互式编辑 DCS 中的动态配置,例如把 loop_wait 调小、开启同步模式
patronictl -d /etc/patroni.yml edit-configloop_wait: 5
ttl: 20
retry_timeout: 10
synchronous_mode: true
synchronous_node_count: 1
postgresql:
use_pg_rewind: true
parameters:
wal_level: replica
hot_standby: "on"
wal_log_hints: "on"
max_wal_senders: 10
max_replication_slots: 10++ Are you sure you want to apply these changes? [y/N]: y
++ Successfully applied the new configuration, reloading...保存后 Patroni 逐节点把参数下发到 postgresql.conf:可 reload 的参数立即生效,需要重启的参数会标记为 pending restart,由下一次 patronictl restart 完成生效。
示例五:维护模式与单节点重启
# 升级系统补丁前:暂停自动故障转移,防止维护期间误切换
patronictl -d /etc/patroni.yml pause
# 逐节点重启(主库重启会触发计划内降级提升,Patroni 自动处理)
patronictl -d /etc/patroni.yml restart pg-prod node3 --force
patronictl -d /etc/patroni.yml restart pg-prod node1 --force
patronictl -d /etc/patroni.yml restart pg-prod node2 --force
# 恢复自动故障转移
patronictl -d /etc/patroni.yml resume示例六:etcd 三节点集群的部署要点
Patroni 的 DCS 前置是一个独立课题,这里给出 etcd v3 三节点的最小部署骨架,作为 Patroni 部署清单的第一项:
# 每台 etcd 节点(以 etcd1 / 192.168.1.21 为例,其余节点替换名字与地址)
etcd \
--name etcd1 \
--data-dir /var/lib/etcd \
--listen-client-urls http://192.168.1.21:2379 \
--advertise-client-urls http://192.168.1.21:2379 \
--listen-peer-urls http://192.168.1.21:2380 \
--initial-advertise-peer-urls http://192.168.1.21:2380 \
--initial-cluster "etcd1=http://192.168.1.21:2380,etcd2=http://192.168.1.22:2380,etcd3=http://192.168.1.23:2380" \
--initial-cluster-token pg-prod-etcd \
--initial-cluster-state new# 任意节点上验证集群仲裁完整
etcdctl endpoint status --cluster -w table+-----------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+-----------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 192.168.1.21:2379 | 8e9e05c52164694d | 3.5.x | 20 kB | true | false | 2 | 12 | 12 | |
| 192.168.1.22:2379 | 91bc3c398fb3c146 | 3.5.x | 20 kB | false | false | 2 | 12 | 12 | |
| 192.168.1.23:2379 | fd422379fda50e48 | 3.5.x | 20 kB | false | false | 2 | 12 | 12 | |
+-----------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+部署清单上的检查项:三台节点的 --initial-cluster 参数完全一致;2379(客户端)对 PostgreSQL 节点开放、2380(peer)只在 etcd 节点间互通;数据目录独立磁盘;endpoint status 中有且仅有一个 IS LEADER = true。生产环境建议再叠加 TLS 与鉴权,细节以 etcd 官方文档为准。
注意事项
ttl / loop_wait / retry_timeout 决定误切换概率。 三者越小转移越快,但对网络抖动越敏感。跨机房部署时把 ttl 调大(如 60),本机房内 30 甚至更低,不要照抄任何「标准值」。
maximum_lag_on_failover 不是零数据丢失的保证。 它只过滤「落后太多的候选」;异步模式下提升后仍可能丢掉未到达的 WAL。要 RPO=0 必须配合 synchronous_mode。
动态参数与本地参数冲突时以动态配置为准。 手工改 postgresql.conf 会被 Patroni 的下一轮循环覆盖;一切全局参数变更走 edit-config,不要直接编辑数据目录里的配置文件。
重启需要重启的参数要显式执行。 edit-config 修改了需重启的参数后,Patroni 只标记 pending restart,不会自动重启实例——计划内重启用 patronictl restart。
pause 期间 Patroni 不做任何转移决策。 维护模式下集群像普通流复制集群一样工作,主库故障不会自动转移;维护结束记得 resume,并确认没有遗留的 pending 操作。
pg_rewind 的前提必须提前满足。 use_pg_rewind: true 需要 wal_log_hints: on(或数据页校验和)与可用的 rewind 账号;在集群已经跑起来之后才补救,意味着旧主库可能已经无法自动回卷。
failover 命令是紧急手段。 它不检查旧主库是否真的死亡,误用会造成双主风险;计划内操作一律用 switchover。
watchdog 设备要在建集群时规划。 mode: required 下没有可用 watchdog 设备的节点拒绝成为主库;临时测试环境可用 softdog 内核模块,生产环境优先使用真实硬件看门狗。
DCS 集群要独立部署、独立监控。 etcd/Consul 与数据库共用主机是常见反模式:资源竞争会同时拖垮大脑与数据,故障域也耦合在一起。
不要绕过 Patroni 直接操作 PostgreSQL。 pg_ctl start/stop/promote、手工 pg_rewind 都会与 Patroni 的状态机冲突;哪怕排障时,也优先用 patronictl 或 REST API 完成等价操作。
节点名、scope 命名要一次定好。 name 与 scope 写入 DCS 后变更成本高(remove 级别的操作),部署前规划好命名规范。
Patroni 版本升级要与 PostgreSQL 升级分开做。 同时升级编排层与数据库层会让故障定位维度翻倍;每次只动一层,升级前完整演练一次切换。
最佳实践
DCS 三节点起步、独立部署、独立监控。 etcd 3 节点是大多数规模的甜点位;把 DCS 可用性纳入告警,它是集群的「大脑」。
全局参数进 DCS(edit-config),节点差异进本地 parameters。 保持「一处修改、全集群生效」的单一事实来源,杜绝节点间配置漂移。
开启
use_pg_rewind并提前满足其前提。wal_log_hints: on与 rewind 账号在 bootstrap 阶段就位,故障转移后旧主库才能自动回卷重归,避免重新做基础备份。对数据丢失零容忍的业务启用同步模式。
synchronous_mode: true+ 合理的synchronous_node_count;strict只在明确接受写入停摆风险时开启。同步语义参见synchronous_standby_names。生产环境配置 watchdog(fencing 终点)。 DCS 仲裁 + watchdog 双保险,才能覆盖「主库与 DCS 分区」的残余脑裂形态。
HAProxy 用 REST API 做角色路由,不要用 VIP 脚本。
httpchk GET /primary是零维护的角色识别方案;VIP 方案只在无 HAProxy 的简单拓扑中使用。把 switchover 演练固化成季度动作。 计划内切换是最安全的验证手段:转移耗时、旧主库回卷、应用重连、PgBouncer 恢复,全链路记录并对比上期。演练设计参见灾难恢复演练。
监控盯三类信号:角色漂移、复制延迟、DCS 仲裁。
patronictl list的时间线与延迟列、pg_stat_replication、etcd 集群健康,三张图覆盖绝大多数事故前兆。维护窗口用 pause/resume 包住。 补丁、扩容、系统升级期间暂停自动转移,完成后立即恢复,并把「是否仍在 pause」纳入每日巡检。
所有运维操作走 patronictl / REST API。 保持 Patroni 状态机的权威性;绕过它的手工操作迟早会在下一次转移时变成事故。
节点标签善用 tags。 异地备库
nofailover、克隆源clonefrom、不参与同步的节点nosync——用声明式标签表达节点的角色预期。Kubernetes 环境直接采用 postgres-operator。 自行在容器里拼 Patroni 不如用社区维护的 Operator,其探针、PVC、滚动升级都已完成集成。
下一节
继续学习:repmgr 复制管理