生产环境的数据库不允许单点故障。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 架构与原理
- etcd 存集群状态:谁是主库、谁是备库、当前配置
- Patroni 守护进程:每节点一个,管理 PostgreSQL 启停、监控健康
- Leader 选举:基于 etcd 的分布式锁,
ttl = 30s内未续租 → 触发选举 - 回调钩子:发生 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 高可用工具对比
| 工具 | 选主机制 | 回调支持 | 复杂度 | 推荐场景 |
|---|---|---|---|---|
| Patroni | etcd/ZooKeeper | ✅ 丰富 | 中等 | 生产首选,社区最大 |
| pg_auto_failover | 内置监控 | ✅ | 低 | 简单高可用场景 |
| repmgr | 自定义流复制 | ✅ | 低 | 中小规模,级联复制 |
| Stolon | etcd/Consul | ✅ | 高 | Kubernetes 环境 |
四、备份策略矩阵
4.1 四种备份方式对比
| 备份类型 | 工具 | 备份内容 | 恢复时间 | 用途 |
|---|---|---|---|---|
| 逻辑全量 | pg_dump / pg_dumpall | SQL 语句可移植 | 分钟~小时 | 单库备份、跨版本迁移 |
| 物理基础 | pg_basebackup | 完整数据目录 | 分钟 | 冷备份、备库初始化 |
| WAL 归档 | archive_command | WAL 段文件 | 秒~分 | 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)
同步复制下主库写入变慢怎么办?
- 改用
synchronous_commit = remote_write(只等备库收到 WAL 但无需重演) - 仅对关键事务用
SET synchronous_commit = remote_apply,其他事务用local - 备库就近部署、使用万兆网络
逻辑复制延迟很大的原因?
- 大事务(如一次 UPDATE 百万行)阻塞解码 → 拆分为小事务
- 订阅端 I/O 瓶颈 → SSD / increase
max_sync_workers_per_subscription - 宽表字段过多 → 只复制必要字段
Patroni 故障转移时应用如何切换?
推荐方案:应用连接 HAProxy/VIP 而非直连 PostgreSQL。Patroni 触发 failover 后,HAProxy health check 发现旧 Primary 不可用,将流量切到新 Primary,应用无感知。
pg_dump 和 pg_basebackup 选哪个?
| 需求 | 推荐 |
|---|---|
| 跨版本恢复、数据迁移 | pg_dump |
| 全实例冷备份、备库初始化 | pg_basebackup |
| 日常备份 | 两者结合(pg_dump 每日 + basebackup 每周 + WAL 归档) |
相关阅读
- PostgreSQL 详解 — MVCC、WAL、流复制原理
- PostgreSQL Docker 部署与初始化 — 容器化配置
- PostgreSQL 性能优化 — EXPLAIN、索引、VACUUM
- PostgreSQL 安全与权限管理 — RLS、SSL、审计
- PostgreSQL 专题导航
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。
「database」更多文章
PostgreSQL 性能优化:从查询计划到硬件调优的完整方法论
详解 PostgreSQL 性能优化的完整方法论:EXPLAIN ANALYZE 查询计划深度解读、索引选择策略(B-tree/GIN/BRIN/部分索引)、VACUUM 与 Autovacuum 调优、连接池与并发控制、postgresql.conf 参数调优矩阵、慢查询定位与优化实战案例。
Prisma + PostgreSQL 实战:Schema 设计、Migration、类型安全查询与 Seed 数据
详解 Prisma ORM 与 PostgreSQL 的完整工作流:Schema 设计、Migration 版本控制、Seed 数据生成、关联查询与事务、Raw SQL 查询、Middleware 拦截器、连接池配置、Next.js/Nest.js 集成,以及 Prisma Studio 可视化调试。
PostgreSQL Docker 部署与初始化:开发到生产的完整配置手册
详解 PostgreSQL 的 Docker 化部署:从基础 docker-compose、开发环境初始 Schema,到生产级参数调优、PgBouncer 连接池、监控(Grafana + Prometheus)、备份策略(pg_dump + pg_basebackup),提供全链路配置清单。