PostgreSQL 高可用教程(流复制 + Patroni 生产级实战)

PostgreSQL 高可用教程:从流复制到 Patroni 生产级架构

Oracle 有 RAC + Data Guard,SQL Server 有 Always On,PostgreSQL 靠什么扛高可用?答案是一套"组合拳":流复制(主备同步能力)+ Patroni(自动故障转移大脑)+ etcd(集群状态存储)+ HAProxy(接入层)。很多 DBA 搭过主备却不敢切换,用过 Patroni 却搞不清它为什么能防止脑裂。这篇教程,从原理到落地,把 PG 高可用整条链路讲透。


一句话总结

PG 高可用方案图谱 = WAL 日志复制(流复制/逻辑复制)做数据同步 + Patroni 或 repmgr 做自动故障转移 + etcd 做分布式锁防脑裂 + 连接池/负载均衡做读写路由 。生产级目标:RPO≈0(同步模式)、RTO≤30 秒,且主备切换全程无需手工干预------把"切库"从 30 分钟变成 30 秒。


一、先建立正确认知:PG 高可用全景图

1.1 高可用四件套

复制代码
                    ┌─────────────────────────────┐
                    │       应用/客户端            │
                    └──────────────┬──────────────┘
                                   ▼
                    ┌─────────────────────────────┐
                    │  接入层: HAProxy / PgBouncer │  ← 路由到当前主库,屏蔽切换
                    └──────────────┬──────────────┘
                                   ▼
                    ┌─────────────────────────────┐
                    │  控制面: Patroni + etcd      │  ← 选主、探测、防脑裂
                    └──────────────┬──────────────┘
                                   ▼
      ┌────────────┐      ┌────────────┐      ┌────────────┐
      │  主库 PG   │ ───▶ │  备库1 PG  │ ───▶ │  备库2 PG  │   ← 流复制
      └────────────┘ WAL  └────────────┘ WAL  └────────────┘

各层职责一句话:

组件 职责 失效时
流复制/逻辑复制 主备数据同步 备库跟不上了(延迟)
Patroni / repmgr 探测主库、自动选主、执行切换 主库挂了自动切换
etcd / Consul / ZK 存集群状态 + 分布式锁 它挂了,Patroni 不敢乱切
HAProxy / PgBouncer 连接路由(只把写流量打到主库) 应用连不上,但库没事

1.2 方案对比(选型表)

方案 同步方式 自动切换 防脑裂 复杂度 适用场景
裸流复制 物理 WAL ❌ 手动 无 ★★ 学习、容灾只读
Patroni + etcd 物理流复制 ✅ ✅ 强一致锁 ★★★★ 生产首选
repmgr 物理流复制 ✅(可选) 有限 ★★★ 轻量自动切换
逻辑复制 WAL 逻辑解码 ❌ 无 ★★★ 跨版本、异构、按表订阅
共享虚拟 IP 方案 依赖外部脚本 ✅ 简单脚本 无 ★★ 简单主备+VIP
Citus / 自动分片 分布式 部分 有 ★★★★★ 大规模 HTAP 场景

结论:单机无脑 Patroni;双机房容灾可以 Patroni + 双 etcd 集群;只要"手动能切"就别把它当高可用。


二、核心概念:WAL、同步/异步、RPO/RTO

2.1 WAL 是 PG 高可用的地基

PG 把一切变更先记入 WAL(Write-Ahead Log,预写日志),再落数据页。备库拿到主库的 WAL 段文件重放,就实现了"数据一样"。

复制代码
主库                   备库
  │                     │
  │  事务提交 ─────────▶ │
  │  写入WAL            │  收到WAL
  │  checkpoint        │  重放WAL
  │       │            │      │
  │   wal_sender进程 ──▶  wal_receiver进程
复制类型 传输内容 特点
物理流复制(推荐) WAL 原始字节 整个集群一致,最简单可靠,不能挑表
逻辑复制 逻辑变更(INSERT/UPDATE/DELETE 等) 可跨版本、可挑表、可做数据变换,但复杂些

2.2 同步 vs 异步(RPO 的核心)

模式 主库提交行为 RPO RTO 场景
异步 写完自己的 WAL 就返回 丢最后几秒数据 30s~分钟 大部分业务
同步 等备库确认收到 WAL 才返回 0(零丢失) 30s~分钟 金融、账务核心
允许降级同步 备库失联自动退异步 取决于备库状态 30s~分钟 兼顾可用性与零丢失

同步≠不丢 :只有一个备库时,它挂了同步就卡死主库(无物可等)------所以同步模式必须有"降级策略"(synchronous_standby_names 变体)或双备库保障。


三、实战一:物理流复制主备搭建(PG 17)

目标:主库 192.168.1.11 + 备库 192.168.1.12,示例两台均以 Rocky 9 + PG17 部署。

3.1 主库配置(两台都先装好 PG,见上一篇文章)

bash 复制代码
# 主库 postgresql.conf 追加
wal_level = replica            # 流复制必需(PG14+ 默认 replica)
max_wal_senders = 10           # 最多允许几个 wal_sender(≥备库数+归档)
wal_keep_size = 1024           # 保留多少 WAL(MB),防止备库追不上
hot_standby = on               # 备库允许只读查询
# 同步模式(可选,按需解注):
# synchronous_standby_names = 'pgstandby1'   # 备库 application_name
bash 复制代码
# 主库 pg_hba.conf 追加:允许备库流复制连接(复制权限用户)
host    replication     repluser        192.168.1.12/32    scram-sha-256
sql 复制代码
-- 主库创建复制专用账号(密码不能为空)
CREATE USER repluser WITH REPLICATION LOGIN PASSWORD 'Repl@2026';

重启主库使其生效:

bash 复制代码
sudo systemctl restart postgresql-17

3.2 备库 basebackup 拉全量数据

bash 复制代码
# 备库:先停掉(若已启动)
sudo systemctl stop postgresql-17

# 用 pg_basebackup 从主库拉一份全量数据(清空原数据目录)
sudo rm -rf /var/lib/pgsql/17/data/*   # 空目录即可,initdb 都不用做
sudo -u postgres pg_basebackup -h 192.168.1.11 -p 5432 -U repluser \
    -D /var/lib/pgsql/17/data -Fp -P -R -X stream

# -R 自动生成备用配置(standby.signal 文件),-X stream 流式传 WAL
cat /var/lib/pgsql/17/data/standby.signal    # 有它就代表"我是备库"

3.3 备库参数与启动

bash 复制代码
# 备库 postgresql.conf 追加
hot_standby = on
primary_conninfo = 'host=192.168.1.11 port=5432 user=repluser password=Repl@2026 application_name=pgstandby1'

启动备库并验证:

bash 复制代码
sudo systemctl start postgresql-17

# 备库检查复制状态
sudo -u postgres psql -c "SELECT pg_is_in_recovery();"   -- 返回 t = 是备库

# 主库检查流复制会话(应有 1 行 synced/async)
sudo -u postgres psql -c "SELECT client_addr, state, sync_state FROM pg_stat_replication;"

3.4 验证:写入主库,备库可见

sql 复制代码
-- 主库
CREATE TABLE t_demo(id int primary key, name text);
INSERT INTO t_demo VALUES (1, 'hello-ha');
sql 复制代码
-- 备库(hot_standby 下可以直接查)
SELECT * FROM t_demo;   -- 应返回 1 行 → 复制成功

-- 备库不能写,试一下会报错:
-- INSERT INTO t_demo VALUES (2,'x');  -- ERROR: cannot execute INSERT in a read-only transaction

到这里,一个"手动版高可用"就完成了:主库挂了,你去备库执行 pg_ctl promote(或 systemctl)它就变成新主库。但手动切换有 10 分钟+ 的 RTO,而且容易出错------下一步上 Patroni 把它自动化。


四、升级生产级:Patroni 架构与搭建

4.1 Patroni 是什么

Patroni 是 Zalando 开源的 PG 高可用控制器,现在已是 PG 生态事实标准的自动故障转移方案。核心机制:

复制代码
Patroni 组件 = Agent(每台 PG 一个)
             + DCS(etcd/consul/zookeeper,分布式共识存储)
  • Agent:每台机器装一个,负责启停 PG、探测健康、执行切换
  • DCS:存"谁是主库"的元数据 + 分布式锁。选主/防脑裂全靠它
  • 脑裂防护 :备库升主前必须先到 DCS 抢到锁(leader key),抢不到就不准升------从机制上杜绝"双主"
DCS 选项 优点 缺点
etcd 使用最广、文档多、性能好 需要多一个 etcd 集群(3 节点+)
Consul 自带服务发现 稍重
Zookeeper 老牌稳定 维护成本略高
内嵌版(raft) 单机学习 无多节点共识,不防脑裂

4.2 架构规划

复制代码
                ┌───────────── VIP: 192.168.1.20 ─────────────┐
                ▼                                             ▼
           ┌─────────┐  etcd 集群(3台,共识)       ┌─────────┐
           │ HAProxy  │                            │ HAProxy │
           └────┬────┘                            └────┬────┘
                │                                      │
        ┌───────┴────────┐                     ┌──────┴────────┐
   ┌────┴─────┐    ┌─────┴─────┐          ┌────┴─────┐    ┌────┴─────┐
   │ PG-1 主   │    │ PG-2 备   │          │ etcd-1   │    │ etcd-2  ...│
   │ patroni  │    │ patroni   │          │ etcd-3   │
   └──────────┘    └───────────┘          └──────────┘

最少 3 台 PG + 3 台 etcd(可合并部署:PG-1 带 etcd-1......)。本文演示单机房 3 节点:PG-1 / PG-2 / PG-3,每台也运行一个 etcd 实例。

4.3 部署步骤(每台 PG 都做)

bash 复制代码
# 1. 安装 etcd(三台都要,集群模式)
sudo dnf install -y etcd
# 配置 /etc/etcd/etcd.conf.yml,以节点1为例:
#   name: etcd1
#   initial-cluster: etcd1=http://192.168.1.11:2380,etcd2=http://192.168.1.12:2380,etcd3=http://192.168.1.13:2380
#   initial-advertise-peer-urls: http://192.168.1.11:2380
#   listen-client-urls: http://192.168.1.11:2379,http://127.0.0.1:2379
sudo systemctl enable --now etcd
etcdctl member list    # 3 个成员 all healthy

# 2. 安装 Patroni(需要 Python3 + pip)
curl -sSL https://bootstrap.pypa.io/get-pip.py | python3 -
python3 -m pip install patroni[etcd] python-etcd psycopg2-binary
patroni --version

4.4 写 Patroni 配置 /etc/patroni.yml(三台内容基本相同,name 不同)

yaml 复制代码
scope: pg-cluster                 # 集群名,三台一致
namespace: /pg/                   # etcd 中的 key 前缀
name: pg1                         # 本机角色名(pg1/pg2/pg3 各自改)

restapi:
  listen: 192.168.1.11:8008       # Patroni REST API 端口
  connect_address: 192.168.1.11:8008

etcd:
  hosts: 192.168.1.11:2379,192.168.1.12:2379,192.168.1.13:2379

bootstrap:
  dcs:
    ttl: 30                       # 租约过期时间(秒)= 故障发现窗口
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 33554432   # 备库延迟>32MB WAL 则禁止升主
    postgresql:
      use_pg_rewind: true         # 重归主库用 pg_rewind,避免全量重建
      parameters:
        wal_level: replica
        hot_standby: "on"
        max_wal_senders: 10
        anytime_sync_standbys: 2     # 同步备库数
        synchronous_standby_names: "*"   # PG17 同步优先备库
  initdb:
    - encoding: UTF8
    - data-checksums: true

postgresql:
  listen: 192.168.1.11:5432
  connect_address: 192.168.1.11:5432
  data_dir: /var/lib/pgsql/17/data
  pgpass: /tmp/pgpass0
  authentication:
    replication:
      username: replicator
      password: "Repl@2026"
    superuser:
      username: postgres
      password: "Postgres@2026"
    rewind:
      username: rewinduser
      password: "Rewind@2026"
  callbacks:
    on_role_change: /usr/local/bin/notify_dba.sh   # 角色变化时通知(可选)

Patroni 会替你 initdb (首台)并生成角色库与用户,请把数据目录先清空、不要手动 initdb。它也接管 systemd:装好后由 patroni.service 管理 PG 进程。

4.5 创建并启动服务(三台顺序执行)

bash 复制代码
# 系统服务文件 /etc/systemd/system/patroni.service
[Unit]
Description=PostgreSQL High Availability (Patroni)
Wants=network-online.target
After=network-online.target etcd.service

[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni.yml
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=process
TimeoutSec=30

[Install]
WantedBy=multi-user.target
bash 复制代码
# 依次在 pg1 → pg2 → pg3 启动
sudo systemctl daemon-reload
sudo systemctl enable --now patroni

# 查看集群状态(任一台执行)
patronictl -c /etc/patroni.yml list
复制代码
+ Cluster: pg-cluster -----+---------+----+-----------+
| Member | Host  | Role    | State   | TL | Lag in MB |
+--------+-------+---------+---------+----+-----------+
| pg1    | .11   | Leader  | running |  1 |           |
| pg2    | .12   | Replica | running |  1 |         0 |
| pg3    | .13   | Replica | running |  1 |         0 |
+--------+-------+---------+---------+----+-----------+

TL(Timeline,时间线)很关键:每次切主 +1。复制协议要求"新主的时间线永远往上涨",所以切换后老主库必须删除或走 pg_rewind 回归同一时间线(Patroni 自动处理)。


五、验证高可用:演练故障转移

5.1 优雅切换(switchover)

bash 复制代码
# 主备互换(先切到 pg2 看看)
patronictl -c /etc/patroni.yml switchover pg-cluster --master pg1 --candidate pg2

patronictl -c /etc/patroni.yml list    # pg2 变 Leader,pg1 变 Replica

5.2 故障转移(failover,主库"死"了才触发)

bash 复制代码
# 模拟主库宕机:把 pg2 的 patroni 进程杀掉(不是优雅停止)
sudo systemctl stop patroni        # 等效于 kill -9 主库进程

# 等 30~40 秒(ttl=30 + loop_wait=10),观察
patronictl -c /etc/patroni.yml list
# 应看到 pg3 自动升为 Leader(或按同步优先级),pg2 显示 Stop

5.3 老主库回归(Patroni 的"再生"能力)

bash 复制代码
# 重新启动 pg2 的 Patroni
sudo systemctl start patroni
patronictl -c /etc/patroni.yml list
# pg2 会自动 pg_rewind 回同步,重新以 Replica 加入集群,无需人工重建
操作 命令 影响
查看状态 patronictl list 只读
优雅切换 patronictl switchover 数据零丢失,业务无感(需应用重连)
故障切换 自动触发 RPO=0(同步),RTO≈30~60s
手动指定候选 switchover --candidate pg2 指定升主对象
暂停监控 patronictl pause 升级维护时用

必做之事 :每季度做一次真实 failover 演练(杀掉主库进程那种,不是优雅停)。没演练过的"高可用"等于没有------和备份必须演练恢复一个道理。


六、接入层:HAProxy + 读写分流

Patroni 只管"谁当主",应用访问还得靠接入层。HAProxy 通过 Patroni 的 REST API(8008 端口)判断健康状态。

6.1 脚本化健康检查

bash 复制代码
# /etc/haproxy/check_pg.sh ------ 只有"我是 leader"才返回 200
#!/bin/bash
curl -sf "http://127.0.0.1:8008/patroni" -H "X-Requested-With: haproxy" | grep -q '"role":"master"' \
  && echo "200 OK" || echo "503 Unavailable"

6.2 HAProxy 配置(写流量只发主库)

复制代码
# /etc/haproxy/haproxy.cfg
frontend pg_write
    bind *:5000
    default_backend pg_master

backend pg_master
    balance roundrobin
    option httpchk GET /patroni
    http-check expect status 200
    server pg1 192.168.1.11:5432 check inter 3s rise 1 fall 2 port 8008
    server pg2 192.168.1.12:5432 check inter 3s rise 1 fall 2 port 8008
    server pg3 192.168.1.13:5432 check inter 3s rise 1 fall 2 port 8008

6.3 读流量:走备库还是要 PgBouncer

读写分流方案 原理 注意
PgBouncer 连接池(池化+路由),应用连它不连 PG 也解决"大量短连接打爆 PG"问题
HAProxy 读后端 健康检查只认 Replica 查询走备库,写仍走主
应用层路由 代码里分读写数据源 灵活但要改代码

生产建议组合:应用 → PgBouncer(读写端口/只读端口)→ HAProxy → Patroni/PG。连接池是必须项------PG 每连接约 5MB 内存,几百个闲置连接足以拖垮一台 32G 机器。


七、扩展知识:逻辑复制、级联与容灾布局

7.1 逻辑复制(跨版本/异构同步)

sql 复制代码
-- 主库:发布
CREATE PUBLICATION pub_orders FOR TABLE orders, order_items;

-- 备库:订阅(可在 PG 16 订阅 PG 17,跨大版本)
CREATE SUBSCRIPTION sub_main
CONNECTION 'host=192.168.1.11 port=5432 user=repuser dbname=mydb'
PUBLICATION pub_orders;
场景 用物理复制 用逻辑复制
集群内主备 ✅ 推荐 可选
跨大版本同步 ❌ ✅(17→16、16→14 都行)
只同步部分表 ❌ ✅
跨平台/异构 ❌ ✅(Oracle/Mongo 也常见)
升级迁移(PG 17→19) ❌ ✅ 或 pg_dump

7.2 级联复制与多机房

复制代码
数据中心 A(主)             数据中心 B(容灾)
┌──────────┐     异步     ┌──────────────┐
│ PG-A 主   │ ───────────▶ │ PG-B1 级联备  │
└────┬─────┘              └──────┬───────┘
     │                           │
┌────▼─────┐                ┌────▼───────┐
│ PG-A2 备 │ ← 同机房同步    │ PG-B2 备    │
└──────────┘                └────────────┘
布局要点 说明
同机房双节点 同步复制,RPO=0
跨机房容灾节点 异步(网络延迟大,同步会卡业务写)
级联备库 减少主库 wal_sender 压力,但备库延迟会累积
容灾接管 灾备中心用 Patroni + 对端 etcd,一键 promote

7.3 WAL 归档与容灾兜底

bash 复制代码
# archive_command 简化版(主/备都配)
archive_mode = on
archive_command = 'test ! -f /backup/pgwal/%f && cp %p /backup/pgwal/%f'
restore_command = 'cp /backup/pgwal/%f %p'

# 备份工具选型
pg_basebackup  ------ 整库物理备份(简单)
pgbackrest     ------ 生产级:增量、加密、并行(推荐)
barman         ------ 老牌,rpm 方便

高可用的最后一道底牌永远是备份:复制 ≠ 备份(误删、恶意操作会顺带复制过去)。Patroni 管"活着",pgbackrest 管"能救回来"。


八、高频故障与排查表

症状 根因 处理
备库一直 streaming 但 Lag 增长 WAL keep 不够 / 网络慢 调 wal_keep_size、开启 pg_basebackup -X stream、查网
pg_wal 目录爆满 备库断连太久,主库攒了太多 WAL 重建备库 / 扩大 max_wal_size 并查复制连接
Patroni 反复 leader 漂移 etcd 抖动 / ttl 太短 加大 ttl,查 etcd 健康:etcdctl endpoint health
双主(脑裂) DCS 锁没抢到也 promote 了 检查 etcd 是否过半存活;确认 maximum-lag-on-failover 配置
切主后应用写不进去 VIP/HAProxy 未指向新主 HAProxy 健康检查探针必须走 /patroni 接口
备库 promote 不了 hot_standby 没开 / 同步备地址没写对 检查 standby.signal 与 primary_conninfo
同步模式下主库卡住 同步备库挂了,等它确认 配置 synchronous_standby_names = 'FIRST 1 (*)' 允许多备选优
切主后老主重新注册报错 时间线冲突 必须 pg_rewind(Patroni 用 use_pg_rewind 自动处理)
bash 复制代码
# 万能排查四连
patronictl -c /etc/patroni.yml list          # 角色、状态、延迟
etcdctl --endpoints=http://127.0.0.1:2379 get /pg/pg-cluster/leader   # 谁在锁上
psql -c "SELECT client_addr,state,sync_state FROM pg_stat_replication;"  # 复制会话
journalctl -u patroni -f                     # Patroni 日志

脑裂防护的核心三条(面试经常问):

机制 作用
分布式锁(etcd leader key) 只有抢到锁的节点能升主
maximum-lag-on-failover 延迟过大的备库无权升主
pg_rewind(Timeline 对齐) 老主回归时强制双向对齐,杜绝双主状态残留

九、和 Oracle RAC / Data Guard 的对照

能力 Oracle RAC Data Guard PostgreSQL 方案
共享存储集群 ✅(同实例并发访问) ❌ ❌(PG 无共享存储集群,单实例模式)
主备数据同步 - ✅ DG(redo 应用) ✅ 流复制(WAL 应用)
自动故障转移 ✅(幸存节点接管) ⚠️ FSFO(Data Guard Broker) ✅ Patroni + etcd
只读扩展 -(PDB 只读) Active Data Guard ✅ hot_standby + 只读路由
脑裂防护 共享存储仲裁 + 心跳 Broker 仲裁 etcd 分布式锁
RTO 典型值 分钟级(实例崩溃自动恢复) 30s~几分钟 30s~1 分钟(Patroni)
升级不停机 Rolling Rolling upgrade 逻辑复制/级联滚动升级

认知差异 :Oracle 的"高可用"分两层(RAC 扛实例、DG 扛数据),PG 用 Patroni 一层全包了------因为它没有"多实例并发一个库"的能力,本质是 "standby 快速接管 + 自动 failover" 路线,和 MySQL 的思路更像。设计时别拿 RAC 的心理预期套 PG。


十、上线前自检清单

markdown 复制代码
□ 1. 主备同版本(大版本不允许混)
□ 2. 备库 ≥ 1 台,延迟常态 < 1MB
□ 3. Patroni:etcd 3 节点健康、ttl 调优、use_pg_rewind=true
□ 4. 同步模式确认(业务能接受丢几秒 → 异步;不能 → 同步+降级策略)
□ 5. HAProxy/PgBouncer:探针走 /patroni,切换后自动漂移已验证
□ 6. 做过真实 failover 演练(杀进程那种)+ 归档切换演练
□ 7. 备份与复制并存:pgbackrest 每夜增量,恢复演练通过
□ 8. 监控:复制延迟 / Patroni 状态 / etcd 健康进告警平台
□ 9. 事故预案:写清楚 RPO/RTO 目标、联系人、回退路径
□ 10. 时间同步:所有节点 NTP 一致(st_clock 偏差会误判)

DBA 夜话 :PG 高可用和 Oracle 最大的不同,是它把"高可用"从买来的产品变成了自己搭的工程 ------流复制给你数据同步,Patroni 给你自动切换,etcd 给你防脑裂的底气,HAProxy 给你业务无感。这四样拼起来不难,难的是像对待备份一样对待演练:每季度杀一次主库,让每个新来的 DBA 亲眼看一次 failover 从 30 秒切换到业务恢复,比文档里写一百遍都管用。记住一句话:**高可用不是配置出来的,是演练出来的------没摔过跟头的集群,迟早摔给生产看。**夜话至此,愿你的集群永远只有一个主。

相关推荐
Dola_Zou1 小时前
从二进制加固到离线鉴权 工业边缘AI安全防御架构
人工智能·安全·ai·架构·软件加密
牧瀬クリスだ1 小时前
Redis String深度解析:从底层到五大实战场景
数据库·redis·缓存
Dawson Zhu1 小时前
【AI编译器入门②】TVM架构拆解:Relay、Relax、TIR与Runtime四层结构
人工智能·语言模型·架构·aigc·agi
Ruiery1 小时前
Linux 6.6内核内存管理深度解析(三·补):页表项(PTE)结构与地址翻译
linux·运维·服务器
平头哥~1 小时前
数字人主题化实践(十):婚庆策划主题的拱门对称布局与多模态感知、端到端编排
ai·ai编程
阿狸猿1 小时前
AI Agent自动写自动化脚本
运维·人工智能·自动化
五块钱的方便面1 小时前
ubuntu下networkmanager不让管理解决办法
linux·运维·ubuntu
云飞云共享云桌面1 小时前
苏州半导体工厂研发部门多个SolidWorks 如何共享服务器来进行研发设计
运维·服务器·3d·自动化·制造
呉師傅1 小时前
Win7剪切文件变成复制文件如何修改【资源管理器异常类故障】
运维·网络·windows·电脑