PostgreSQL 流复制实战:用 Patroni 实现高可用主从集群

PostgreSQL 流复制(Streaming Replication)是实现数据库高可用的基石。它把主库(Primary)产生的 WAL 日志实时推送给从库(Standby),让从库在磁盘层面与主库保持一致,一旦主库宕机就能快速提升从库接管服务。但手工维护主从、处理故障切换(Failover)既繁琐又容易出错——这正是 Patroni 的用武之地。本文带你用 Patroni + etcd 搭建一套能自动选主、自动故障切换的 PostgreSQL 高可用主从集群,并给出可直接复制的完整配置与运维排障清单。

在动手之前,先厘清一个常被混淆的概念:流复制保证的是”数据同步”,而高可用保证的是”服务不中断”。前者由 PostgreSQL 内核完成,后者才需要 Patroni 这类外部管理器来编排。二者职责分明,下面的内容会依次把它们讲透。

一、流复制到底复制了什么

PostgreSQL 的流复制属于物理复制:它复制的是 WAL(Write-Ahead Log)二进制日志,而不是 SQL 语句。主库每产生一条 WAL 记录,就通过 TCP 连接推送给已连接的从库,从库重放这些记录,从而在数据文件层面与主库保持字节级一致。

与PostgreSQL 逻辑复制按表、按行复制不同,流复制是实例级、近实时的,常用于读写分离、异地容灾与高可用底座。理解两者的区别,是设计复制架构的第一步。

1.1 同步复制还是异步复制

流复制默认是异步的:主库提交事务后不等待从库确认就返回成功,性能最好,但主库宕机可能丢失最后几笔未送达从库的事务。把 synchronous_standby_names 配置上之后就变成同步复制:主库必须等至少一个同步从库确认收到 WAL 才提交,换来零数据丢失,代价是写延迟上升。怎么选,看下面这张对照表:

维度异步复制同步复制
数据一致性可能丢失秒级内事务主从强一致,零丢失
写性能几乎无额外开销受从库网络/磁盘影响
适用场景大多数业务、读多写少金融交易、账目类核心
# 指定一个同步从库(其余作为潜在候选)
synchronous_standby_names = 'pg-node2'

二、为什么需要 Patroni

2.1 手工主从的痛点

纯流复制只能保证”数据同步”,并不解决”谁是当前主库”。当主库宕机时,DBA 需要人工介入:确认主库真的死了、提升某个从库、修改应用连接串、防止脑裂。在大促或深夜故障中,这几步极易出错,且恢复时间(RTO)完全取决于人。

2.2 Patroni 的架构

Patroni 是一个用 Python 写的模板化高可用管理器。它把”谁是主库”这一关键信息存放在一个分布式配置存储(DCS,通常是 etcd、Consul 或 ZooKeeper)里,通过租约(TTL)机制自动选举 leader,并在主库失联时把某个健康的从库提升为新主。关于 etcd 本身的运维,可参考etcd 实战。整体组件规划如下:

2.3 自动故障切换的真实流程

理解切换链路,才能在出事时不慌:① 主库进程挂掉或网络隔离,心跳超时;② etcd 中该 leader 的租约到期被释放;③ 存活节点中数据最新的那个(由 maximum_lag_on_failover 控制容忍滞后量)被 Patroni 选举为 Leader 并执行 pg_promote 提升;④ 应用经 HAProxy/服务发现连接到新主。整条链路通常在 10~30 秒内完成,期间写入会短暂阻塞,这正是我们要用 watchdog 把故障窗口压到最小的原因。

节点IP角色
pg-node110.0.0.11PostgreSQL + Patroni
pg-node210.0.0.12PostgreSQL + Patroni
etcd-node1~310.0.0.21~23etcd DCS 集群(奇数节点)

三、先打通流复制基础

无论是否用 Patroni,流复制自身的参数都要先配好。主从节点共用以下 postgresql.conf 关键项:

# 主从通用
listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
hot_standby = on
wal_keep_size = 1GB

在 pg_hba.conf 中放行复制用户的连接(replication 是特殊数据库名):

# pg_hba.conf
host    replication     repl        10.0.0.0/24    md5

主库创建专用复制账号,从库用 pg_basebackup 拉取基础备份。注意 -R 参数会自动生成 standby.signal 与 primary_conninfo,省去手工配置:

# 主库:创建复制用户
CREATE ROLE repl WITH REPLICATION PASSWORD 'strongpassword' LOGIN;

# 从库:拉取基础备份并自动成为 standby
pg_basebackup -h 10.0.0.11 -U repl -D /var/lib/postgresql/data \
  -Fp -Xs -P -R

四、部署 etcd 作为 DCS

Patroni 需要 etcd 来存放集群状态与 leader 锁。三个 etcd 节点组成集群,配置示例(etcd-node1):

# /etc/etcd/etcd.conf.yml
name: etcd-node1
data-dir: /var/lib/etcd
initial-advertise-peer-urls: http://10.0.0.21:2380
listen-peer-urls: http://10.0.0.21:2380
advertise-client-urls: http://10.0.0.21:2379
listen-client-urls: http://10.0.0.21:2379,http://localhost:2379
initial-cluster: etcd-node1=http://10.0.0.21:2380,etcd-node2=http://10.0.0.22:2380,etcd-node3=http://10.0.0.23:2380
initial-cluster-token: pg-etcd-cluster
initial-cluster-state: new

五、用 Patroni 接管集群

Patroni 的配置文件描述”期望状态”,它会自己初始化数据库、创建复制关系并持续守护。下面给出 pg-node1 的 patroni.yml:

scope: pg-cluster
namespace: /pg/
name: pg-node1

restapi:
  listen: 10.0.0.11:8008
  connect_address: 10.0.0.11:8008

etcd:
  hosts:
    - 10.0.0.21:2379
    - 10.0.0.22:2379
    - 10.0.0.23:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      parameters:
        wal_level: replica
        max_wal_senders: 10
        hot_standby: "on"
  initdb:
    - encoding: UTF8
    - data-checksums

postgresql:
  listen: 10.0.0.11:5432
  connect_address: 10.0.0.11:5432
  data_dir: /var/lib/postgresql/data
  authentication:
    replication:
      username: repl
      password: strongpassword
    superuser:
      username: postgres
      password: supassword

tags:
  nofailover: false
  noloadbalance: false

第二个节点只需改 name、listen、connect_address 和 data_dir 即可。启动后 Patroni 会在 etcd 中注册自己,并自动把其中一个节点初始化为主库。

六、验证故障切换

用 patronictl 查看集群状态,确认一主一从:

patronictl -c /etc/patroni.yml list
# + Cluster: pg-cluster ----+--------+---------+----+-----------+
# | Member    | Host        | Role   | State   | Lag (s)   |
# | pg-node1  | 10.0.0.11   | Leader | running |           |
# | pg-node2  | 10.0.0.12   | Replica| running |         0 |

模拟主库宕机,观察自动切换:

# 在 pg-node1 上停止 Patroni(连带停库)
sudo systemctl stop patroni
sleep 15
# 在任一存活节点查看:pg-node2 已被提升为 Leader
patronictl -c /etc/patroni.yml list

七、让应用无感知地连接高可用

应用不应该直连某个固定 IP,否则切换后还要改配置。常见做法是前面挂一个 HAProxy,让它只把写流量导向当前 Leader,读流量负载到所有 Replica:

# haproxy.cfg(片段)
listen pg-read-write
    bind *:5000
    mode tcp
    option pgsql-check user postgres
    server pg1 10.0.0.11:5432 check
    server pg2 10.0.0.12:5432 check

更优雅的方案是结合 Patroni 的 REST API 做服务发现,让 HAProxy 动态感知 leader 变化,避免手动维护后端列表。

八、运维排障清单

高可用系统最怕”平时不演练,故障时抓瞎”。把下面这张表贴在值班文档里:

现象可能原因处理
从库追赶不上主库主库写入压力大 / 网络带宽不足调大 wal_keep_size、检查网络、考虑级联复制
Patroni 拿不到 leader 锁etcd 不可达 / 节点时钟漂移检查 etcd 集群、统一 NTP 时间
切换后出现脑裂旧主未真正停写 / 缺少 fencing配置 watchdog、旧主恢复后作为 standby 重新加入
复制延迟突增大事务 / 从库长事务阻塞拆分大事务、终止从库上的长事务

九、和其他 PG 能力的配合

流复制与实例内的优化并不冲突:你依然可以对大表做PostgreSQL 分区表来提升查询与维护效率,用慢查询分析定位性能瓶颈。流复制解决”可用性”,分区与索引解决”性能”,二者叠加才是生产级数据库的正确姿态。

对于超大数据量,单一主从集群之外还可以叠加逻辑复制到数据仓库、或用分库分表中间件做水平扩展——但高可用底座永远是第一位的,否则再强的性能也扛不住一次宕机。

十、复制延迟监控与容量规划

高可用集群上线后,复制延迟是最该被盯紧的指标。直接查系统视图就能拿到主从差距(以 WAL 字节数计):

SELECT
  client_addr,
  state,
  pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes,
  round(extract(epoch FROM (now() - reply_time))::numeric, 1) AS lag_sec
FROM pg_stat_replication;

把这条查询接入Prometheus + Grafana 监控做成看板,一旦 lag 持续攀升就要警惕:通常是主库突发大事务、从库上有长事务阻塞重放,或网络带宽被打满。容量规划上,给 WAL 归档和 wal_keep_size 预留充足磁盘,并定期用 pg_rewind 处理老主回切时的数据分歧——Patroni 配置里的 use_pg_rewind: true 正是为此,它能在旧主重新加入时自动把时间线对齐,避免手工重建。

结语

PostgreSQL 流复制提供了”数据同步”的能力,而 Patroni + etcd 把”自动故障切换”补齐,二者结合才构成一个真正高可用的主从集群。把本文的配置跑通一次,再做一次真实的 kill -9 演练,你就能在故障发生时淡定地喝口水——因为系统已经在你睡觉的时候完成了切换。

上一篇 OPA 实战:用 Open Policy Agent 统一策略授权与决策
下一篇 Vitest 实战:Vite 原生前端单元测试框架
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]