Skip to content

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

基础概念 ​

概念英文 / 记法说明
DCSDistributed Configuration Store分布式配置存储,leader key 所在,集群状态的唯一权威
leader keyleader keyDCS 中的键,持有其租约的节点即为主库
租约 / TTLlease / ttlleader key 的有效期,主库须持续续期,过期即视为失效
选举electionleader key 过期后,存活节点竞争接替的过程
工作循环loopPatroni 每 loop_wait 秒执行一轮:健康检查、续租、对齐配置
REST APIrestapiPatroni 的 HTTP 接口(默认 8008),健康检查与运维入口
patronictlpatronictlPatroni 的命令行管理工具
动态配置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 的完整示例:

yaml
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 与首次初始化:

bash
# 首个节点:bootstrap 会执行 initdb 并写入 DCS 初始配置
patroni /etc/patroni.yml

# 其余节点:直接启动,Patroni 自动从主库 pg_basebackup 克隆数据目录
patroni /etc/patroni.yml

patronictl 是日常运维的主通道(-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 中的信息(高危)
bash
patronictl -d /etc/patroni.yml list
text
+ 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 秒执行一轮工作循环:

text
┌─────────────── 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 键:

text
/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 秒。选举过程:

  1. 主库进程崩溃或与 DCS 失联,leader 键在 ttl 秒后过期;
  2. 各备库节点在下一轮循环发现 leader key 已过期,进入候选状态;
  3. 候选者自检:本机数据足够新(落后不超过 maximum_lag_on_failover 字节)、时间线可参与竞争;
  4. 竞争获得 leader key 的节点执行提升(pg_promote),更新 DCS 中的成员信息;
  5. 其余节点观察到新主库,重新指向它继续流复制。

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 键前缀、本节点唯一名本地文件
restapiREST 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 调整:

参数默认值说明
ttl30leader key 租约时长(秒),决定故障发现的下限
loop_wait10工作循环间隔(秒),绝大多数操作的最小粒度
retry_timeout10与 DCS 交互及复制连接的超时(秒)
maximum_lag_on_failover1048576参与提升的备库允许的最大落后字节数(默认 1MB)
master_start_timeout300主库启动等待超时(秒),超时则放弃等待转入转移
synchronous_modefalse是否开启同步复制模式
synchronous_mode_strictfalse严格同步:无可用同步备库时阻止提交
synchronous_node_count1同步备库数量
standby_cluster无将整组备库组织为级联的 standby cluster(异地容灾)
postgresql.use_pg_rewindfalse旧主库重新加入时是否允许 pg_rewind 回卷
postgresql.use_slotstrue是否由 Patroni 管理物理复制槽

三者的关系值得记住:ttl ≥ loop_wait + retry_timeout,故障发现时间约等于 ttl,整体 RTO 约 ttl + 提升 + 应用重连,把 ttl 从 30 调到 15 可以显著缩短转移窗口,但也会放大网络抖动引起的误切换,需结合机房网络质量决定。

故障转移流程详解 ​

把一次完整的主库故障转移按时间轴拆开:

text
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:

yaml
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
/replicaGET是备库返回 200(可带 lag 参数校验延迟)
/sync / /asyncGET是同步/异步备库返回 200
/read-onlyGET可提供只读服务返回 200
/healthGET节点健康返回 200(不区分角色)
/leaderGET是主库(或 standby cluster 主库)返回 200
/switchover / /failoverPOST发起计划内/紧急切换(JSON 指定 leader 与 candidate)
/configGET / PATCH读取/修改动态配置
/historyGET故障转移历史
/metricsGETPrometheus 格式指标
/liveness / /readinessGETKubernetes 探针
bash
# 健康检查三连:分别用于写路由、读路由与存活探测
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 5
text
200
503
200

pg_rewind 集成、脑裂防护与 watchdog ​

pg_rewind 集成。故障转移后旧主库重新上线时,它的数据目录可能与新主库存在时间线分叉(多写了几个事务)。use_pg_rewind: true 让 Patroni 自动用 pg_rewind 把旧主库回卷到分叉点再重新加入,替代了「扔掉数据目录重新做基础备份」的粗放做法。启用前提有两个,缺一不可:wal_log_hints 设为 on(或建库时启用数据页校验和),以及一个具备相应权限的 rewind 认证账号。pg_rewind 的完整用法以 PostgreSQL 官方文档为准(见文末参考链接)。

脑裂与 fencing。Patroni 的第一道防线是 DCS 仲裁:没有租约的节点不会把自己提升为主库,已降级的节点写操作会被拒绝,这从机制上排除了「双主」的大多数情形。但有一类残余风险:主库与 DCS 之间网络分区时,它知道自己可能已丢租约,但无法确定是否真的过期——若此时它继续服务,与新主库形成短暂双写窗口。watchdog 就是针对这种情形的最后防线:

yaml
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 单元托管:

ini
[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.target

Docker:官方仓库提供镜像与 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计划外变更告警

实战示例 ​

示例一:从零搭建三节点集群 ​

bash
# 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
text
+ 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) ​

bash
# 交互式切换,指定候选节点;旧主库自动降级并跟随
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' --force
text
Current 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。

示例三:模拟主库故障,观察自动转移 ​

bash
# 直接杀掉主库节点的 PostgreSQL(模拟崩溃)
sudo pkill -9 -f 'postgres -D'

# 在另一节点上观察转移过程(约 ttl + 提升耗时)
watch -n 2 'patronictl -d /etc/patroni.yml list'
text
+ 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 可以查到这次转移的时间线记录。

示例四:修改动态配置(全集群生效) ​

bash
# 交互式编辑 DCS 中的动态配置,例如把 loop_wait 调小、开启同步模式
patronictl -d /etc/patroni.yml edit-config
yaml
loop_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
text
++ 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 完成生效。

示例五:维护模式与单节点重启 ​

bash
# 升级系统补丁前:暂停自动故障转移,防止维护期间误切换
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 部署清单的第一项:

bash
# 每台 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
bash
# 任意节点上验证集群仲裁完整
etcdctl endpoint status --cluster -w table
text
+-----------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|     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 升级分开做。 同时升级编排层与数据库层会让故障定位维度翻倍;每次只动一层,升级前完整演练一次切换。

最佳实践 ​

  1. DCS 三节点起步、独立部署、独立监控。 etcd 3 节点是大多数规模的甜点位;把 DCS 可用性纳入告警,它是集群的「大脑」。

  2. 全局参数进 DCS(edit-config),节点差异进本地 parameters。 保持「一处修改、全集群生效」的单一事实来源,杜绝节点间配置漂移。

  3. 开启 use_pg_rewind 并提前满足其前提。 wal_log_hints: on 与 rewind 账号在 bootstrap 阶段就位,故障转移后旧主库才能自动回卷重归,避免重新做基础备份。

  4. 对数据丢失零容忍的业务启用同步模式。 synchronous_mode: true + 合理的 synchronous_node_count;strict 只在明确接受写入停摆风险时开启。同步语义参见synchronous_standby_names。

  5. 生产环境配置 watchdog(fencing 终点)。 DCS 仲裁 + watchdog 双保险,才能覆盖「主库与 DCS 分区」的残余脑裂形态。

  6. HAProxy 用 REST API 做角色路由,不要用 VIP 脚本。 httpchk GET /primary 是零维护的角色识别方案;VIP 方案只在无 HAProxy 的简单拓扑中使用。

  7. 把 switchover 演练固化成季度动作。 计划内切换是最安全的验证手段:转移耗时、旧主库回卷、应用重连、PgBouncer 恢复,全链路记录并对比上期。演练设计参见灾难恢复演练。

  8. 监控盯三类信号:角色漂移、复制延迟、DCS 仲裁。 patronictl list 的时间线与延迟列、pg_stat_replication、etcd 集群健康,三张图覆盖绝大多数事故前兆。

  9. 维护窗口用 pause/resume 包住。 补丁、扩容、系统升级期间暂停自动转移,完成后立即恢复,并把「是否仍在 pause」纳入每日巡检。

  10. 所有运维操作走 patronictl / REST API。 保持 Patroni 状态机的权威性;绕过它的手工操作迟早会在下一次转移时变成事故。

  11. 节点标签善用 tags。 异地备库 nofailover、克隆源 clonefrom、不参与同步的节点 nosync——用声明式标签表达节点的角色预期。

  12. Kubernetes 环境直接采用 postgres-operator。 自行在容器里拼 Patroni 不如用社区维护的 Operator,其探针、PVC、滚动升级都已完成集成。

下一节

继续学习:repmgr 复制管理

参考链接 ​