PostgreSQL 架构原理第五期:备份与恢复 —— 物理备份、PITR 与复制技术

0 阅读11分钟

PostgreSQL 架构原理第五期:备份与恢复 —— 物理备份、PITR 与复制技术

引言

在前四期中,我们从进程模型、存储引擎、事务与并发控制,到查询优化器,逐步深入了解了 PostgreSQL 的内核原理。数据是企业的核心资产,如何保证数据不丢失、如何从灾难中快速恢复、如何实现 7×24 小时高可用,是数据库运维的必修课。

本期将聚焦 PostgreSQL 的备份与恢复体系,涵盖以下内容:

  1. 逻辑备份与物理备份的对比与适用场景
  2. 基于 WAL 的持续归档与时间点恢复(PITR)原理
  3. pg_basebackup 与低级别基础备份的实现机制
  4. 物理复制(流复制)与高可用架构(主备、同步/异步、故障切换)
  5. 逻辑复制的原理、用途与与物理复制的差异
  6. 备份恢复的最佳实践与常见陷阱

一、备份方法概览:逻辑备份 vs 物理备份

特性逻辑备份物理备份
工具pg_dump / pg_dumpallpg_basebackup、文件系统快照
备份内容SQL 语句或自定义格式的归档数据目录的完整二进制文件(包括 WAL 文件)
恢复粒度可恢复单个表或数据库只能整个集群恢复(不能跨版本或选择性恢复)
恢复速度慢(需要重放 SQL 并重建索引)快(直接复制文件,应用 WAL)
并行能力可并行(-j 参数)支持并行传输
适用场景小数据量、跨版本升级、部分数据导出大数据量、灾难恢复、高可用搭建
一致性保证基于快照(SERIALIZABLE基于 WAL 的全局一致性(需要归档或使用复制协议)

核心结论:

  • 日常开发、小规模迁移用逻辑备份。
  • 生产环境中,基于物理备份 + WAL 归档的 PITR 方案是最可靠的容灾手段。

二、物理备份基础:数据目录与 WAL 文件

PostgreSQL 的物理备份本质上是文件系统级别的复制,但必须保证复制出的文件处于一致性状态。单纯用 cprsync 复制正在运行的数据库的数据目录,会得到不一致的备份(部分已刷盘的页面 vs 部分未刷盘)。为了解决这个问题,PostgreSQL 提供了两种方法:

  1. 低级别 API:使用 pg_start_backup()pg_stop_backup() 配合文件系统工具。
  2. 高层工具pg_basebackup 内部使用复制协议,自动完成上述步骤。

2.1 低级别基础备份流程

-- 1. 开始备份,创建一个标签文件(backup_label)
SELECT pg_start_backup('label_name');

-- 2. 使用外部工具拷贝整个数据目录(可以跳过 pg_wal 目录,但需要另外归档)
cp -R $PGDATA /backup/base

-- 3. 结束备份,自动生成备份历史文件
SELECT pg_stop_backup();
  • pg_start_backup 执行检查点,强制将当前 WAL 位置写到 backup_label 文件中,并记录起始 LSN。
  • 拷贝期间数据库可以继续读写,但所有修改都会写入 WAL。
  • pg_stop_backup 生成一个备份历史文件(.backup),其中包含备份的起始/结束 LSN 以及所需 WAL 文件列表。恢复时需要这些 LSN 信息。

这种方法的优点是不依赖 pg_basebackup,适合结合 ZFS、LVM 快照或 rsync 脚本实现定制化备份。

2.2 pg_basebackup 工具

pg_basebackup 使用复制协议连接到 PostgreSQL 主库,自动完成 pg_start_backup、数据传输和 pg_stop_backup。常用选项:

pg_basebackup -D /path/to/backup -F t -z -X fetch -P -U repluser -h host
  • -F t:输出为 tar 包;-F p 为普通目录。
  • -X fetch:备份过程中同时获取所需的 WAL 段文件(stream 模式更实时)。
  • -P 显示进度。

注意pg_basebackup 要求备份用户具备 REPLICATION 权限,并且需要启用 max_wal_senders > 0


三、持续 WAL 归档与时间点恢复(PITR)

物理备份本身是某一时刻的静默拷贝,要恢复到最新状态或任意时间点,必须配合增量 WAL 日志

3.1 WAL 归档的原理

postgresql.conf 中设置:

archive_mode = on
archive_command = 'cp %p /archive/%f'   # 将完成的 WAL 段文件复制到归档目录

每当 WAL 段文件被写满(默认 16MB)切换时,PostgreSQL 会执行 archive_command 将文件保存到永久归档位置。归档完成后才能重复使用该段文件。

恢复时,PostgreSQL 会从基础备份开始,按顺序重放归档中的 WAL 段文件,前滚到指定时间点。

3.2 时间点恢复(PITR)步骤

  1. 准备基础备份:将备份文件拷贝到数据目录。
  2. 配置恢复参数:在数据目录创建 recovery.signal(PostgreSQL 12+)和 postgresql.auto.confrecovery.conf(旧版本)。
    restore_command = 'cp /archive/%f %p'
    recovery_target_time = '2025-03-15 14:30:00'
    recovery_target_action = 'promote'
    
  3. 启动数据库:实例进入恢复模式,自动应用 WAL 直到指定的目标时间、事务 ID 或 LSN。
  4. 提升为主库:到达目标点后,数据库停止应用并接受连接(若设置了 promote)。

恢复时还可以使用 recovery_target_xidrecovery_target_lsnrecovery_target_inclusive 控制边界。

3.3 归档的注意事项

  • 归档命令必须返回零退出码表示成功。建议在命令中加入 rsyncscp 或上传到对象存储(如 S3)。
  • 确保归档目录的磁盘空间充足,定期清理过旧的归档(通过 pg_archivecleanuprecovery_end_command)。
  • 如果 WAL 归档延迟或丢失,将无法完全恢复。可以使用同步备库配合 synchronous_commit 增强数据可靠性。

四、流复制:从异步到同步的高可用

流复制(Streaming Replication)是 PostgreSQL 最常用的高可用方案。备库通过 WAL 接收进程实时获取主库生成的 WAL 记录,并立即重放到备库中,实现接近实时的数据同步。

4.1 流复制架构

  • 主库wal_level = replicalogicalmax_wal_senders 至少为 1,配置 primary_conninfo 在备库中指向主库。
  • 备库:从基础备份恢复,配置 primary_conninfo,创建 standby.signal 文件(PostgreSQL 12+)或设置 standby_mode = on
  • 复制槽:可选,用于防止主库在备库未接收 WAL 时移除仍需要的 WAL 段。

流复制的副本可以是只读的,接受查询(称为热备),但不能执行写操作。可以设置多个备库实现负载分摊。

4.2 同步与异步复制

  • 异步复制:主库提交事务后,无需等待备库确认即返回客户端。性能好,但故障时可能丢失少量数据。
  • 同步复制:需等待至少一个同步备库写入磁盘(由 synchronous_standby_names 指定),返回客户端前确保数据已在备库持久化。
synchronous_commit = on
synchronous_standby_names = 'FIRST 1 (standby1)'   # 等待第一个同步备库

同步复制能保证零数据丢失(RPO=0),但会引入延迟和写吞吐量下降。

4.3 流复制内部机制

主库上的 WAL 发送进程(walsender)不断从 WAL 缓冲区或磁盘上读取 WAL 记录,发送给备库的 walreceiver 进程。备库收到后写入本地 WAL,再由启动进程重放到数据文件。

关键参数:

  • wal_keep_size(旧版 wal_keep_segments):主库保留的 WAL 段数量,避免备库落后太多时 WAL 被覆盖。
  • max_slot_wal_keep_size:为复制槽额外保留的 WAL 上限。
  • hot_standby:备库是否接受只读查询。

4.4 故障切换与灾难恢复

当主库故障时,可以手工或通过集群管理工具(如 Patroni、repmgr)将一个备库提升为新主库。提升操作:

-- 在备库执行
pg_ctl promote   -- 或 SELECT pg_promote();
  • 旧主库若恢复,可作为新备库重新加入(需重新设置 primary_conninfo 并重建)。
  • 为避免脑裂,通常需要配合仲裁机制(etcd、ZooKeeper)或使用 Pacemaker。

五、逻辑复制:基于表级的数据分发

与物理复制复制整个数据库集群不同,逻辑复制允许选择性地复制特定表,甚至在不同大版本之间复制数据(常用于升级)。

5.1 核心概念

  • 发布者(Publisher):定义哪些表的哪些变更需要发布(可细分为 INSERT、UPDATE、DELETE、TRUNCATE)。
  • 订阅者(Subscriber):订阅一个或多发布者,接收变更并应用。
  • 复制标识(REPLICA IDENTITY):每个表必须设置,用于标识 UPDATE/DELETE 时的行(默认主键或 FULL)。
-- 发布者
CREATE PUBLICATION mypub FOR TABLE t1, t2;
-- 订阅者
CREATE SUBSCRIPTION mysub CONNECTION 'host=pub_host dbname=test' PUBLICATION mypub;

5.2 逻辑复制与物理复制的区别

特性物理复制逻辑复制
复制粒度整个实例表级、行级选择性复制
版本兼容性必须相同主版本(可能需要小版本一致)不同大版本(如 14 到 16)可以复制
DDL 复制自动复制所有 DDL不复制 DDL(需手动在订阅端执行)
冲突处理无冲突(物理一致)可能出现冲突(唯一约束、重复行等),需要用户处理
数据筛选可以指定 WHERE 条件(仅部分行)
双向复制(多主)不支持可借助扩展(如 pglogical)实现有限的双向复制
初始同步pg_basebackup 全量拷贝CREATE SUBSCRIPTION 时自动拷贝现有数据

逻辑复制的典型应用:数据聚合到数据仓库、跨版本在线升级、多主架构(需谨慎设计冲突解决)。

5.3 逻辑复制的内部流程

  1. 发布端通过 walsender 进程读取 WAL,利用解码插件(pgoutputtest_decoding)将 WAL 转换为逻辑变更消息。
  2. 订阅端 apply 进程接收消息,并在本地执行 SQL 以重放变更。
  3. 工作过程基于 LSN 跟踪,保证精确一次交付(at-least-once)和断点续传。

六、备份恢复策略与最佳实践

6.1 制定 RPO 与 RTO

  • RPO(数据恢复点目标):可容忍丢失多少数据?零丢失需同步复制或 WAL 归档无延迟。
  • RTO(恢复时间目标):多久恢复业务?物理备份 + PITR 比逻辑备份恢复更快;流复制备库可实现秒级切换。

6.2 备份分层方案

  1. 全量物理备份(每周一次,例如周日凌晨)。
  2. 持续 WAL 归档(实时或每分钟执行 archive_command 或使用 pg_receivewal)。
  3. 逻辑备份(每天一次用于应急或误删除数据恢复)。
  4. 归档到异地(对象存储或远程 NFS),防止单机房灾难。

6.3 恢复演练

  • 定期在测试环境执行完整的 PITR 恢复,验证归档完整性。
  • 演练后检查 pg_wal 目录下的 .history 文件,可跟踪时间线分支(timeline),防止恢复后与旧主库通信混乱。

6.4 常见陷阱与注意事项

  • 事务 ID 回卷:备份中的数据库长时间未运行,再次恢复时可能因 pg_control 中的 xid 远小于当前而被视为过期。需要在备份前确保 VACUUM FREEZE 已经执行。
  • 归档命令失败:导致 WAL 堆积在 pg_wal,耗尽磁盘。监控 pg_stat_archiver 视图,并设置 archive_timeout 强制切换。
  • 备库延迟过大:复制槽会阻止主库清理 WAL,如备库长期故障,主库磁盘可能爆满。设置 max_slot_wal_keep_size 或监控备库落后字节数。
  • 误操作恢复:逻辑备份可以提取单个表;物理备份 + PITR 需要恢复到误操作时间点前。建议保留较长时间的逻辑备份和归档。

七、总结与预告

本期详细介绍了 PostgreSQL 的备份与恢复体系:

  • 逻辑备份与物理备份的选型与原理。
  • 基础备份与 WAL 归档构成 PITR 的核心。
  • 流复制(物理复制)实现了高可用和实时容灾,支持同步/异步模式。
  • 逻辑复制提供了更灵活的发布订阅模型,适合数据分发和跨版本升级。
  • 最佳实践应包括定期恢复演练、异地归档和监控归档/复制状态。

理解这些机制,我们不仅能防范数据丢失,还能设计出满足业务 RPO/RTO 的高可用架构。

第六期将是本次系列的最后一期,我们将探讨 PostgreSQL 性能调优实战,结合前五期的知识,从硬件配置、参数调优、SQL 优化、监控诊断等方面给出系统性的方法论和案例。


思考题

  1. 为什么不建议在生产环境直接使用 pg_dump 作为唯一的备份手段?物理备份相比它有哪些不可替代的优势?
  2. 如果主库磁盘损坏导致 WAL 归档中断,而备库还保持同步,如何恢复主库?切换后如何防止旧主库的错误回切?
  3. 逻辑复制能否完全替代流复制来实现高可用?为什么?