PostgreSQL 高可用与备份:流复制、Patroni 自动故障转移与 PITR 恢复

详解 PostgreSQL 高可用架构:流复制(同步/异步)、逻辑复制(异构表/跨版本)、Patroni + etcd 自动故障转移、备份策略矩阵(pg_dump / pg_basebackup / WAL 归档)、灾难恢复(PITR)完整流程。

生产环境的数据库不允许单点故障。PostgreSQL 的高可用 = 复制(随时有热备)+ 自动故障转移(主库宕机秒级切换)+ 备份(最坏情况可恢复)

本文按复制方式(物理/逻辑)→ 故障转移工具 → 备份策略 → 灾难恢复的链路,提供从单库到集群的完整高可用方案。

一句话总结:物理复制用于高可用(1 主 N 从),逻辑复制用于部分表/异构同步,Patroni 负责自动选主,WAL 归档让你回到任意时间点。


一、流复制(Streaming Replication)

1.1 架构原理

PostgreSQL 流复制基于 WAL(预写日志):主库写入 WAL,备库实时接收并重放。

                Primary(读写主库)
                    │
                    │  WAL 发送 (wal_sender)
                    ▼
    ┌──────────────┼──────────────┐
    │              │              │
Synchronous    Async 1        Async 2
 Standby      (热备)        (只读)
  (零丢)     (秒级延迟)

同步复制 vs 异步复制

维度同步异步
主库确认时机等待备库写入完成后本地 WAL 刷盘后即确认
数据丢失极端情况可能丢失 0 事务可能丢失最近几个事务
主库延迟加(等待网络往返)
使用场景金融核心系统大多数 Web 应用

1.2 主库配置(postgresql.conf)

wal_level = replica              # 启用 WAL 的详细日志(minimal/replica/logical)
max_wal_senders = 10             # 同时发送 WAL 的最大连接数
wal_keep_size = 1GB              # 保留 WAL 的最小量(备库 lag 过大时用)
max_replication_slots = 5        # 逻辑/物理复制槽数上限
hot_standby = on                 # 备库允许只读查询
synchronous_commit = remote_apply # remote_apply = 主库等待备库应用完成
synchronous_standby_names = 'FIRST 1 (s1, s2)'  # 指定同步对象

1.3 备库启动流程

# 1. 从基础备份恢复
pg_basebackup -h primary_host -p 5432 -U repluser -D /var/lib/postgresql/data \
  -Ft -z -P --wal-method=stream

# 2. 创建 standby.signal 文件(让 PostgreSQL 以备模式启动)
touch /var/lib/postgresql/data/standby.signal

# 3. 配置主库连接
# postgresql.auto.conf
primary_conninfo = 'host=primary_host port=5432 user=repluser password=secret application_name=s1'

# 4. 启动备库
pg_ctl -D /var/lib/postgresql/data start

1.4 复制方式对比

方式复制粒度适用限制
物理复制全实例级热备/高可用同版本、同架构
逻辑复制表级异构/跨版本/ETL不复制 DDL
WAL 归档WAL 文件PITR 恢复需配合基础备份

二、逻辑复制(Logical Replication)

2.1 与物理复制的区别

物理复制:Primary WAL ──→ Standby(字节级复制,所有数据库/表)
逻辑复制:Primary WAL ──→ 逻辑解码 ──→ 行数据协议 ──→ Subscriber(表级可筛选)

逻辑复制的优势:

  • 表级选择性:只复制需要的表
  • 异构目标:可汇总到数据仓库(差异版本的 PostgreSQL 或不同数据库)
  • 双向复制:可设置冲突处理策略(但建议单向)

2.2 逻辑复制配置

-- 主库:创建发布(Publish)
CREATE PUBLICATION app_publication FOR TABLE users, posts, orders;
-- 或全表:CREATE PUBLICATION all_tables FOR ALL TABLES;

-- 备库:创建订阅(Subscribe)
CREATE SUBSCRIPTION app_subscription
  CONNECTION 'host=primary_host port=5432 user=repluser password=secret dbname=myapp'
  PUBLICATION app_publication
  WITH (copy_data = true, create_slot = true);

-- 查看复制槽状态
SELECT * FROM pg_replication_slots;

-- 查看订阅状态
SELECT * FROM pg_stat_subscription;

2.3 逻辑复制注意事项

注意点说明
DDL 不会复制CREATE TABLE / ALTER TABLE 需手动在订阅端同步
序列值不复制自增 ID 需单独同步或全局 UUID
大事务延迟逻辑解码对大事务较慢,拆分大事务
冲突处理ON CONFLICT 策略需配置,disable_on_error 可跳过冲突

三、自动故障转移:Patroni + etcd

3.1 故障转移无主之痛

PostgreSQL 原生不支持自动选主——主库挂了,需手动执行 pg_ctl promote 提升备库。Patroni 解决了这个问题。

┌─────────────────┐     ┌─────────────┐
│  Patroni (主)   │     │ Patroni(备) │
│  PostgreSQL     │     │ PostgreSQL  │
│  (Leader)       │     │ (Replica)   │
└────────┬────────┘     └──────┬──────┘
         │                     │
         └────── etcd ─────────┘
         │   (分布式一致性存储)   │
         └────── etcd ─────────┘
                  │
          HAProxy / VIP
                  │
              应用程序

3.2 Patroni 架构与原理

  1. etcd 存集群状态:谁是主库、谁是备库、当前配置
  2. Patroni 守护进程:每节点一个,管理 PostgreSQL 启停、监控健康
  3. Leader 选举:基于 etcd 的分布式锁,ttl = 30s 内未续租 → 触发选举
  4. 回调钩子:发生 failover 时执行自定义脚本(更新 DNS/负载均衡/告警)

3.3 Patroni 配置示例

# patroni.yml
scope: mycluster
namespace: /db/
name: node-1

restapi:
  listen: 0.0.0.0:8008
  connect_address: node-1:8008

etcd:
  hosts: etcd-1:2379,etcd-2:2379,etcd-3:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576  # 1MB WAL lag 限制
    postgresql:
      parameters:
        wal_level: replica
        hot_standby: on
        max_connections: 200

postgresql:
  listen: 0.0.0.0:5432
  connect_address: node-1:5432
  data_dir: /var/lib/postgresql/data
  authentication:
    replication:
      username: repluser
      password: secret
    superuser:
      username: postgres
      password: secret

3.4 高可用工具对比

工具选主机制回调支持复杂度推荐场景
Patronietcd/ZooKeeper✅ 丰富中等生产首选,社区最大
pg_auto_failover内置监控简单高可用场景
repmgr自定义流复制中小规模,级联复制
Stolonetcd/ConsulKubernetes 环境

四、备份策略矩阵

4.1 四种备份方式对比

备份类型工具备份内容恢复时间用途
逻辑全量pg_dump / pg_dumpallSQL 语句可移植分钟~小时单库备份、跨版本迁移
物理基础pg_basebackup完整数据目录分钟冷备份、备库初始化
WAL 归档archive_commandWAL 段文件秒~分PITR 任意时间点恢复
逻辑槽复制逻辑复制表级行数据实时异构同步、实时 ETL

4.2 每日逻辑备份脚本

#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=/backups/postgres
DB_NAME=myapp
RETENTION_DAYS=30

# pg_dump 自定义格式(可并行恢复)
pg_dump -Fc -Z 9 -v -d ${DB_NAME} -f ${BACKUP_DIR}/${DB_NAME}_${DATE}.dump

# 全局对象(角色/表空间)
pg_dumpall -r -f ${BACKUP_DIR}/roles_${DATE}.sql

# 清理旧备份(保留 30 天)
find ${BACKUP_DIR} -name "*.dump" -mtime +${RETENTION_DAYS} -delete

4.3 物理备份与 WAL 归档

# 配置归档(postgresql.conf)
archive_mode = on
archive_command = 'test ! -f /backups/wal/%f && cp %p /backups/wal/%f'
wal_level = replica

# 基础备份
pg_basebackup -D /backups/base_$(date +%Y%m%d) \
  -Ft -z -P -h primary -p 5432 -U repluser --wal-method=stream

# 备份完后生成 backup_manifest(PostgreSQL 14+)
# 用 pg_verifybackup /backups/base_20250801 验证一致性

4.4 备份验证 checklist

□ 每周至少一次恢复演练(恢复到测试环境)
□ 验证 pg_dump 备份文件完整性(pg_restore -l 列出内容)
□ 验证 pg_basebackup 完整性(pg_verifybackup)
□ 确认 WAL 归档文件连续(ls 检查时间连续性)
□ 备份文件异地存储(S3 / 其他数据中心)
□ 备份加密(必要时)

五、灾难恢复(PITR)

5.1 PITR(Point-In-Time Recovery)流程

基础备份(前天) + WAL 归档(持续) = 恢复到任意时间点
    ↓
1. 解压基础备份到新数据目录
2. 创建 recovery.signal
3. 配置恢复目标时间点
4. 启动 PostgreSQL,自动回放 WAL 到目标时间点
5. WAL 回放完成后,PostgreSQL 自动退出恢复模式

5.2 PITR 配置

# 1. 解压基础备份
tar xzf /backups/base_20250801.tar.zst -C /var/lib/postgresql/recovery

# 2. 创建恢复信号
touch /var/lib/postgresql/recovery/recovery.signal

# 3. 配置恢复参数(postgresql.conf 或命令行)
# restore_command:PostgreSQL 请求 WAL 文件时调用
cat > /var/lib/postgresql/recovery/postgresql.auto.conf <<EOF
restore_command = 'cp /backups/wal/%f %p'
recovery_target_time = '2025-08-01 14:30:00'
recovery_target_action = 'promote'
EOF

# 4. 启动
cd /var/lib/postgresql/recovery && pg_ctl start
# PostgreSQL 会自动回放 WAL 到 14:30:00,完成后变为 Primary

5.3 PITR 恢复策略

场景恢复方式所需时间
误删单表pg_dump 恢复 + 逻辑修复分钟
误删整个数据库pg_dumpall 恢复分钟~小时
主库硬件故障紧急提升热备 + 重搭新备< 1 分钟
数据被黑客清除PITR 恢复到攻击前时间点小时
数据库文件损坏基础备份 + WAL 恢复小时

六、生产高可用架构推荐

6.1 中小型集群(< 1TB)

Application
    │
    ▼
HAProxy (with health check + failover script)
    │
    ├─▶ Patroni A (Primary, 3 etcd)
    │         PostgreSQL
    │
    ├─▶ Patroni B (Synchronous Standby)
    │         PostgreSQL (Read Replicas)
    │
    └─▶ Patroni C (Asynchronous Standby)
              PostgreSQL (Disaster Recovery Site)

6.2 大型集群(> 1TB / 多地域)

Region A(生产)
  Primary ── Synchronous Standby
    │
    └─▶ Logical Replication ──▶ Region B(灾备)
                                     Subscriber
Region C(归档)
  WAL 归档 S3 + 基础备份

常见问题(FAQ)

同步复制下主库写入变慢怎么办?

  1. 改用 synchronous_commit = remote_write(只等备库收到 WAL 但无需重演)
  2. 仅对关键事务用 SET synchronous_commit = remote_apply,其他事务用 local
  3. 备库就近部署、使用万兆网络

逻辑复制延迟很大的原因?

  1. 大事务(如一次 UPDATE 百万行)阻塞解码 → 拆分为小事务
  2. 订阅端 I/O 瓶颈 → SSD / increase max_sync_workers_per_subscription
  3. 宽表字段过多 → 只复制必要字段

Patroni 故障转移时应用如何切换?

推荐方案:应用连接 HAProxy/VIP 而非直连 PostgreSQL。Patroni 触发 failover 后,HAProxy health check 发现旧 Primary 不可用,将流量切到新 Primary,应用无感知。

pg_dump 和 pg_basebackup 选哪个?

需求推荐
跨版本恢复、数据迁移pg_dump
全实例冷备份、备库初始化pg_basebackup
日常备份两者结合(pg_dump 每日 + basebackup 每周 + WAL 归档)

相关阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「database」更多文章