土法炼钢兴趣小组的算法知识备份

【存储工程】备份策略工程

文章导航

分类入口
storage
标签入口
#backup#full-backup#incremental-backup#wal-archiving#xtrabackup#pg-basebackup#pitr

目录

一个数据库集群运行了三年没出过事,运维团队逐渐把备份当成”例行公事”——每天凌晨跑一次全量备份,脚本跑完就算结束,从来没人验证过备份文件能不能真正恢复。直到某天一个 DBA 误执行了 DROP DATABASE,团队才发现:最近三个月的备份文件全部损坏,因为磁盘空间不足导致 mysqldump 中途退出,但脚本没有检查退出码,监控系统也没有告警。

这不是虚构的场景。GitLab 在 2017 年 1 月 31 日的事故中,五种备份机制全部失效——pg_dump 版本不匹配导致备份为空、LVM 快照从未配置、S3 备份因错误跳过、Azure 备份未启用、WAL 归档目录中只有空文件。最终靠一个 DBA 六小时前偶然做的一次手动备份才恢复了大部分数据,但仍然丢失了约 6 小时的生产数据。

备份不是一个”配置完就不用管”的功能,而是一项需要持续投入的工程实践。本文从备份的工程动机出发,逐一拆解全量/差异/增量三种策略、逻辑与物理备份的权衡、WAL 连续归档的原理,然后分别讨论 MySQL 和 PostgreSQL 的备份方案,最后覆盖备份验证、加密压缩、3-2-1 规则和监控告警。

适用范围 本文讨论的备份策略主要针对关系型数据库(MySQL 8.0、PostgreSQL 16)和通用文件系统备份场景。不涉及分布式数据库(如 TiDB、CockroachDB)的多副本机制——多副本是高可用手段,不能替代备份。不同版本的工具行为可能有差异,文中会标注版本。


一、备份的工程动机

1.1 数据丢失的典型场景

数据丢失不只是硬盘坏了这一种情况。按照触发原因分类,至少有以下几种场景:

场景类别 典型案例 多副本能否防护 备份能否防护
硬件故障 磁盘损坏、控制器故障、整机宕机 能(副本接管)
人为误操作 DROP TABLEDELETE 不带 WHERE、误删数据目录 不能(误操作会同步到副本) 能(恢复到误操作前的时间点)
软件缺陷 数据库 bug 导致数据写坏、升级脚本错误 不能(损坏数据同样会复制) 能(恢复到 bug 触发前)
安全事件 勒索软件加密数据文件、恶意删库 不能(攻击者通常会同时破坏副本) 能(前提是备份和生产环境隔离)
逻辑错误 应用层 bug 导致大量脏数据写入 不能(脏数据正常复制) 能(时间点恢复)
自然灾害 机房火灾、洪水、地震 取决于副本是否跨地域 能(前提是异地备份)

这张表说明一个核心问题:多副本解决的是可用性(Availability),备份解决的是可恢复性(Recoverability)。副本保证系统不停机,备份保证数据不丢失。两者必须同时存在。

1.2 恢复目标的两个指标

备份策略的设计围绕两个核心指标展开:

恢复点目标(Recovery Point Objective,RPO):系统发生故障后,最多允许丢失多长时间的数据。RPO = 0 表示不允许丢失任何数据;RPO = 1 小时表示最多丢失 1 小时内的写入。

恢复时间目标(Recovery Time Objective,RTO):从故障发生到系统恢复服务,最多允许多长时间。RTO = 15 分钟表示必须在 15 分钟内完成恢复。

RPO 决定了备份频率和备份方式——要实现 RPO = 0 通常需要同步复制,RPO = 几分钟可以用 WAL 连续归档,RPO = 24 小时用每日全量备份就够了。RTO 决定了备份格式和恢复流程——物理备份的恢复速度通常远快于逻辑备份。

1.3 备份的成本

备份不是免费的。需要考虑的成本包括:

备份策略的核心就是在 RPO/RTO 要求与这些成本之间找到平衡。


二、备份策略分类

备份策略按照每次备份的数据范围分为三种:全量备份(Full Backup)、差异备份(Differential Backup)和增量备份(Incremental Backup)。

2.1 全量备份

全量备份(Full Backup)每次复制数据集的完整副本。优点是恢复简单——只需要一份备份文件就能还原完整数据。缺点是时间长、占用空间大。

时间轴:
Day 1      Day 2      Day 3      Day 4      Day 5
┌──────┐   ┌──────┐   ┌──────┐   ┌──────┐   ┌──────┐
│ Full │   │ Full │   │ Full │   │ Full │   │ Full │
│100 GB│   │102 GB│   │105 GB│   │107 GB│   │110 GB│
└──────┘   └──────┘   └──────┘   └──────┘   └──────┘

恢复 Day 4 的数据:只需要 Day 4 的全量备份。
总存储量:100 + 102 + 105 + 107 + 110 = 524 GB

2.2 差异备份

差异备份(Differential Backup)每次只备份自上一次全量备份以来发生变化的数据。恢复时需要最近一次全量备份加上最近一次差异备份,共两份文件。

时间轴:
Day 1      Day 2      Day 3      Day 4      Day 5
┌──────┐   ┌──────┐   ┌──────┐   ┌──────┐   ┌──────┐
│ Full │   │ Diff │   │ Diff │   │ Diff │   │ Diff │
│100 GB│   │  2 GB│   │  5 GB│   │  7 GB│   │ 10 GB│
└──────┘   └──────┘   └──────┘   └──────┘   └──────┘
               │           │           │           │
               └───────────┴───────────┴───────────┘
                 都相对 Day 1 的全量备份

恢复 Day 4 的数据:需要 Day 1 全量 + Day 4 差异 = 2 份文件。
总存储量:100 + 2 + 5 + 7 + 10 = 124 GB

差异备份的特点是:随着距离上次全量备份越远,差异备份的体积越大(因为累积的变化越多)。但恢复只需要两步,比增量备份简单。

2.3 增量备份

增量备份(Incremental Backup)每次只备份自上一次备份(不管是全量还是增量)以来发生变化的数据。恢复时需要最近一次全量备份加上之后所有的增量备份,按顺序依次应用。

时间轴:
Day 1      Day 2      Day 3      Day 4      Day 5
┌──────┐   ┌──────┐   ┌──────┐   ┌──────┐   ┌──────┐
│ Full │   │ Incr │   │ Incr │   │ Incr │   │ Incr │
│100 GB│   │  2 GB│   │  3 GB│   │  2 GB│   │  3 GB│
└──────┘   └──────┘   └──────┘   └──────┘   └──────┘
               │           │           │           │
               │     相对 Day 2  相对 Day 3  相对 Day 4
          相对 Day 1

恢复 Day 4 的数据:需要 Day 1 全量 + Day 2 增量 + Day 3 增量 + Day 4 增量 = 4 份文件。
总存储量:100 + 2 + 3 + 2 + 3 = 110 GB

2.4 三种策略对比

维度 全量备份 差异备份 增量备份
每次备份数据量 大(完整数据集) 中(累积变化) 小(最近一次变化)
备份速度 中等
存储占用 中等
恢复复杂度 低(1 份文件) 中(2 份文件) 高(1 + N 份文件)
恢复速度 中等 慢(需逐个应用)
单份备份损坏的影响 只丢失该份 丢失该差异周期 该份之后的所有增量都无法使用

实际生产环境中最常见的做法是混合策略:每周一次全量备份 + 每天一次增量备份,或者每周一次全量备份 + 每天一次差异备份。增量备份的链条越长,恢复越慢且风险越高——中间任何一份损坏,后续的增量就全部失效。因此全量备份的频率不能太低。


三、逻辑备份 vs 物理备份

按照备份内容的表示方式,备份还可以分为逻辑备份(Logical Backup)和物理备份(Physical Backup)。

3.1 逻辑备份

逻辑备份导出的是数据库的逻辑内容——表结构(DDL)和行数据(DML)。典型工具包括 MySQL 的 mysqldumpmysqlpumpmydumper,PostgreSQL 的 pg_dumppg_dumpall

逻辑备份的输出通常是 SQL 语句或特定格式的文本文件,例如:

-- mysqldump 输出片段
CREATE TABLE `orders` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL,
  `amount` decimal(10,2) NOT NULL,
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO `orders` VALUES
(1, 1001, 99.99, '2025-01-15 10:30:00'),
(2, 1002, 149.50, '2025-01-15 11:00:00'),
(3, 1001, 29.99, '2025-01-15 11:30:00');

逻辑备份的优点:

逻辑备份的缺点:

3.2 物理备份

物理备份直接复制数据库的底层数据文件——数据文件、索引文件、WAL/redo log 文件。典型工具包括 MySQL 的 Percona XtraBackup、MySQL Enterprise Backup,PostgreSQL 的 pg_basebackup,以及文件系统级别的 LVM 快照、ZFS 快照。

物理备份的优点:

物理备份的缺点:

3.3 对比总结

维度 逻辑备份 物理备份
备份内容 SQL 语句 / 文本数据 数据文件 / WAL 文件
备份速度
恢复速度 慢(需执行 SQL、重建索引) 快(文件复制 + WAL 回放)
备份粒度 库 / 表 / 行 实例 / 表空间
跨版本恢复 支持 通常不支持
可读性
对业务影响 中(长事务 / 锁) 低(热备)
支持 PITR 不直接支持 支持(配合 WAL)

实际生产环境中,常见的做法是物理备份为主、逻辑备份为辅


四、连续备份与 WAL 归档

全量备份和增量备份都有一个共同的局限:备份之间的时间窗口内,数据变更没有被捕获。如果在两次备份之间发生故障,窗口内的数据就丢失了。WAL 连续归档(Write-Ahead Log Continuous Archiving)解决的就是这个问题。

4.1 WAL 的基本原理

预写日志(Write-Ahead Log,WAL)是数据库保证持久性的核心机制:任何数据修改在写入数据文件之前,必须先写入 WAL。WAL 记录了每一次数据变更的细节,理论上可以用来将数据库从任意一个一致状态”回放”到后续的任意时间点。

MySQL 中对应的概念是二进制日志(Binary Log,binlog)和 InnoDB 重做日志(Redo Log)。PostgreSQL 中就叫 WAL。两者的机制有差异,但核心思路一致。

4.2 连续归档的工作方式

连续归档的思路是:在做周期性全量备份的基础上,把所有 WAL 文件持续地复制到归档存储中。恢复时,先恢复最近一次全量备份,然后从归档中按顺序回放 WAL,就能将数据库恢复到任意时间点。这就是时间点恢复(Point-in-Time Recovery,PITR)。

连续归档工作流:

┌─────────────────────────────────────────────────────────────────────┐
│                          时间轴                                     │
├──────────┬──────────┬──────────┬──────────┬──────────┬──────────────┤
│  Day 1   │  Day 2   │  Day 3   │  Day 4   │  Day 5   │  Day 6      │
│          │          │          │          │          │              │
│ ┌──────┐ │          │          │          │ ┌──────┐ │              │
│ │ Full │ │          │          │          │ │ Full │ │              │
│ │Backup│ │          │          │          │ │Backup│ │              │
│ └──────┘ │          │          │          │ └──────┘ │              │
│          │          │          │          │          │              │
│ ─────────┼──WAL─────┼──WAL─────┼──WAL─────┼──WAL─────┼──WAL──────▶ │
│  归档     │  归档     │  归档     │  归档     │  归档     │  归档       │
├──────────┴──────────┴──────────┴──────────┴──────────┴──────────────┤
│                                                                     │
│  恢复到 Day 3 下午 14:30:                                           │
│  1. 还原 Day 1 的全量备份                                            │
│  2. 回放 Day 1 → Day 3 14:30 的所有 WAL                             │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

连续归档的 RPO 取决于 WAL 归档的频率。如果 WAL 文件一写满就立即归档(PostgreSQL 默认 WAL 段大小 16 MB),RPO 取决于未归档的最后一个 WAL 段的数据量。通过流复制归档(pg_receivewal)或 WAL 流式传输,可以将 RPO 降低到接近零。

4.3 PostgreSQL WAL 归档配置

PostgreSQL 的 WAL 归档通过以下参数控制(postgresql.conf):

# 开启归档模式
archive_mode = on

# 归档命令:每个 WAL 段文件完成后执行
# %p = WAL 文件的完整路径,%f = WAL 文件名
archive_command = 'cp %p /backup/wal_archive/%f'

# WAL 级别必须设为 replica 或 logical
wal_level = replica

# 保留的 WAL 段数量(用于流复制和恢复)
wal_keep_size = 1GB

更可靠的归档命令应该包含校验和错误处理:

#!/bin/bash
# archive_wal.sh —— PostgreSQL WAL 归档脚本
# 用法:archive_command = '/path/to/archive_wal.sh %p %f'

WAL_SOURCE="$1"
WAL_FILENAME="$2"
ARCHIVE_DIR="/backup/wal_archive"
DEST="${ARCHIVE_DIR}/${WAL_FILENAME}"

# 避免覆盖已有的归档文件
if [ -f "${DEST}" ]; then
    echo "WAL file ${WAL_FILENAME} already exists in archive" >&2
    exit 0
fi

# 复制并验证
cp "${WAL_SOURCE}" "${DEST}.tmp" \
    && sync "${DEST}.tmp" \
    && mv "${DEST}.tmp" "${DEST}"

exit $?

4.4 MySQL binlog 的连续归档

MySQL 没有像 PostgreSQL 那样内置的 archive_command 机制,但 binlog 本身就是按文件顺序写入的日志流。连续归档的做法是通过 mysqlbinlog --read-from-remote-server --raw 或专用工具(如 mysqlbinlog --read-from-remote-server 的守护进程模式)实时拉取 binlog 文件:

# 从 MySQL 主库实时拉取 binlog 到本地归档目录
mysqlbinlog \
    --read-from-remote-server \
    --host=db-primary.example.com \
    --port=3306 \
    --user=repl_user \
    --password \
    --raw \
    --stop-never \
    --result-file=/backup/binlog_archive/ \
    mysql-bin.000123

--stop-never 参数让 mysqlbinlog 持续运行,像从库一样不断拉取新的 binlog 事件。这种方式的 RPO 接近于主库写入 binlog 到备份端接收之间的延迟,通常在秒级。


五、MySQL 备份方案

MySQL 生态中的备份工具可以按逻辑/物理维度和在线/离线维度分类。本节讨论三个最常用的工具:mysqldump、Percona XtraBackup 和 binlog 归档。

5.1 mysqldump

mysqldump 是 MySQL 自带的逻辑备份工具,输出 SQL 文本。对于 InnoDB 表,推荐使用 --single-transaction 获取一致性快照:

# 全库逻辑备份(InnoDB 表,一致性快照)
mysqldump \
    --host=127.0.0.1 \
    --port=3306 \
    --user=backup_user \
    --password \
    --single-transaction \
    --routines \
    --triggers \
    --events \
    --set-gtid-purged=ON \
    --all-databases \
    | gzip > /backup/full_$(date +%Y%m%d_%H%M%S).sql.gz

关键参数说明:

mysqldump 的恢复就是执行 SQL 文件:

# 恢复
gunzip < /backup/full_20250108_020000.sql.gz | mysql \
    --host=127.0.0.1 \
    --port=3306 \
    --user=root \
    --password

mysqldump 适合数据量在几十 GB 以下的场景。超过 100 GB 后,备份和恢复时间通常不可接受。

5.2 Percona XtraBackup

Percona XtraBackup 是 MySQL/InnoDB 的开源物理热备工具。它直接复制 InnoDB 的数据文件和 redo log,备份期间不需要锁表(对 InnoDB 表),不影响在线业务。

XtraBackup 的备份过程分两个阶段:

  1. 复制阶段:在后台持续复制 InnoDB 数据文件(.ibd 文件),同时持续监控并复制 redo log 的变化。
  2. 完成阶段:执行 FLUSH TABLES WITH READ LOCK(FTWRL)获取一致性位点,记录 binlog 位置和 GTID,然后释放锁。这个锁持续的时间很短(通常在秒级),远比 mysqldump 的长事务影响小。
# 全量物理备份
xtrabackup \
    --backup \
    --target-dir=/backup/full_20250108 \
    --host=127.0.0.1 \
    --port=3306 \
    --user=backup_user \
    --password=xxx \
    --parallel=4 \
    --compress \
    --compress-threads=4

恢复分两步——先 prepare(应用 redo log 达到一致状态),再复制到数据目录:

# 第一步:prepare(应用 redo log)
xtrabackup --prepare --target-dir=/backup/full_20250108

# 第二步:复制到 MySQL 数据目录(需要先停止 MySQL)
systemctl stop mysqld
rm -rf /var/lib/mysql/*
xtrabackup --move-back --target-dir=/backup/full_20250108
chown -R mysql:mysql /var/lib/mysql
systemctl start mysqld

XtraBackup 还支持增量备份:

# 增量备份(基于之前的全量备份)
xtrabackup \
    --backup \
    --target-dir=/backup/incr_20250109 \
    --incremental-basedir=/backup/full_20250108 \
    --host=127.0.0.1 \
    --port=3306 \
    --user=backup_user \
    --password=xxx

# 恢复增量备份:先 prepare 全量,再逐个应用增量
xtrabackup --prepare --apply-log-only --target-dir=/backup/full_20250108
xtrabackup --prepare --apply-log-only \
    --target-dir=/backup/full_20250108 \
    --incremental-dir=/backup/incr_20250109
# 最后一次 prepare 不加 --apply-log-only
xtrabackup --prepare --target-dir=/backup/full_20250108

5.3 MySQL 时间点恢复

MySQL 的时间点恢复(PITR)依赖 binlog。流程如下:

  1. 从 XtraBackup 的全量备份恢复数据库。
  2. 查看备份记录中的 binlog 位置(在 xtrabackup_binlog_info 文件中)。
  3. mysqlbinlog 将该位置之后的 binlog 回放到目标时间点。
# 查看备份的 binlog 位置
cat /backup/full_20250108/xtrabackup_binlog_info
# 输出示例:mysql-bin.000042    15423    a1b2c3d4-...:1-12345

# 回放 binlog 到指定时间点
mysqlbinlog \
    --start-position=15423 \
    --stop-datetime="2025-01-08 14:30:00" \
    /var/lib/mysql/mysql-bin.000042 \
    /var/lib/mysql/mysql-bin.000043 \
    | mysql --host=127.0.0.1 --port=3306 --user=root --password

5.4 MySQL 备份方案选型

方案 类型 备份速度 恢复速度 支持增量 支持 PITR 锁影响 适用规模
mysqldump 逻辑 不支持 配合 binlog 长事务 < 50 GB
mydumper 逻辑 中等(多线程) 中等 不支持 配合 binlog 短锁 < 200 GB
XtraBackup 物理 支持 配合 binlog 极短锁 TB 级
MySQL Enterprise Backup 物理 支持 配合 binlog 极短锁 TB 级
LVM 快照 物理 极快 极快 不支持 配合 binlog 短锁 TB 级

六、PostgreSQL 备份方案

PostgreSQL 的备份工具体系和 MySQL 有所不同,核心工具包括 pg_dumppg_basebackup,以及基于 WAL 归档的 PITR。

6.1 pg_dump

pg_dump 是 PostgreSQL 自带的逻辑备份工具。与 mysqldump 不同,pg_dump 天然基于 MVCC 快照,不需要显式加锁就能获取一致性视图:

# 单库逻辑备份(自定义格式,支持并行恢复)
pg_dump \
    --host=127.0.0.1 \
    --port=5432 \
    --username=backup_user \
    --dbname=mydb \
    --format=custom \
    --compress=6 \
    --jobs=4 \
    --file=/backup/mydb_$(date +%Y%m%d_%H%M%S).dump

# 全实例备份(所有数据库 + 全局对象)
pg_dumpall \
    --host=127.0.0.1 \
    --port=5432 \
    --username=postgres \
    | gzip > /backup/all_$(date +%Y%m%d_%H%M%S).sql.gz

pg_dump--format=custom 格式(-Fc)是推荐的备份格式,因为它支持:

恢复示例:

# 从自定义格式备份恢复(并行,4 个作业)
pg_restore \
    --host=127.0.0.1 \
    --port=5432 \
    --username=postgres \
    --dbname=mydb \
    --jobs=4 \
    --clean \
    --if-exists \
    /backup/mydb_20250108_020000.dump

6.2 pg_basebackup

pg_basebackup 是 PostgreSQL 自带的物理备份工具,通过流复制协议直接从运行中的 PostgreSQL 实例复制整个数据目录:

# 物理备份(流式传输 WAL,压缩输出)
pg_basebackup \
    --host=127.0.0.1 \
    --port=5432 \
    --username=repl_user \
    --pgdata=/backup/base_20250108 \
    --wal-method=stream \
    --checkpoint=fast \
    --progress \
    --verbose

关键参数说明:

pg_basebackup 从 PostgreSQL 15 开始支持增量备份(需要启用 summarize_wal):

# PostgreSQL 17 增量备份
# 先确保配置了 summarize_wal = on

# 全量备份(作为增量基线)
pg_basebackup \
    --host=127.0.0.1 \
    --port=5432 \
    --username=repl_user \
    --pgdata=/backup/base_full \
    --wal-method=stream \
    --checkpoint=fast

# 增量备份(基于前一次备份的 manifest)
pg_basebackup \
    --host=127.0.0.1 \
    --port=5432 \
    --username=repl_user \
    --pgdata=/backup/base_incr_day2 \
    --incremental=/backup/base_full/backup_manifest \
    --wal-method=stream \
    --checkpoint=fast

6.3 PostgreSQL PITR

PostgreSQL 的 PITR 基于全量物理备份加 WAL 归档。恢复流程如下:

# 第一步:从 pg_basebackup 恢复数据目录
systemctl stop postgresql
rm -rf /var/lib/postgresql/16/main/*
cp -a /backup/base_20250108/* /var/lib/postgresql/16/main/

然后创建恢复配置。PostgreSQL 12 及以后版本使用 postgresql.conf 中的恢复参数:

# postgresql.conf 中添加恢复参数
restore_command = 'cp /backup/wal_archive/%f %p'
recovery_target_time = '2025-01-08 14:30:00'
recovery_target_action = 'promote'

并创建恢复信号文件:

# 创建恢复信号文件
touch /var/lib/postgresql/16/main/recovery.signal
chown postgres:postgres /var/lib/postgresql/16/main/recovery.signal

# 启动 PostgreSQL,开始恢复
systemctl start postgresql

PostgreSQL 会自动从归档中获取 WAL 文件,按顺序回放,直到达到 recovery_target_time 指定的时间点,然后提升为主库。

6.4 第三方工具

PostgreSQL 生态中还有多个成熟的备份管理工具:

pgBackRest 的使用示例:

# pgBackRest 配置(/etc/pgbackrest/pgbackrest.conf)
# [global]
# repo1-path=/backup/pgbackrest
# repo1-retention-full=2
# repo1-cipher-type=aes-256-cbc
# repo1-cipher-pass=backup-encryption-key
# process-max=4
# compress-type=zst
# compress-level=3
#
# [mydb]
# pg1-path=/var/lib/postgresql/16/main

# 全量备份
pgbackrest --stanza=mydb --type=full backup

# 增量备份
pgbackrest --stanza=mydb --type=incr backup

# 差异备份
pgbackrest --stanza=mydb --type=diff backup

# 恢复到指定时间点
pgbackrest --stanza=mydb --type=time \
    --target="2025-01-08 14:30:00" \
    restore

6.5 PostgreSQL 备份方案选型

方案 类型 备份速度 恢复速度 支持增量 支持 PITR 远程存储 适用规模
pg_dump 逻辑 慢(可并行) 不支持 不支持 手动 < 100 GB
pg_basebackup 物理 PG 17 支持 配合 WAL 手动 TB 级
pgBackRest 物理 快(并行) 快(并行) 支持 内置 S3/GCS/Azure TB 级
Barman 物理 支持 内置 S3/GCS TB 级
WAL-G 物理 支持 内置 S3/GCS/Azure TB 级

七、备份验证与恢复测试

备份文件存在不等于备份可用。未经验证的备份和没有备份本质上没有区别。

7.1 备份验证的层次

备份验证可以分为四个层次,从低到高:

第一层:文件完整性校验。检查备份文件是否完整、未损坏。最基本的做法是计算校验和(如 SHA-256)并与备份时记录的校验和对比。

# 备份时记录校验和
sha256sum /backup/full_20250108.tar.gz > /backup/full_20250108.sha256

# 验证时比对
sha256sum -c /backup/full_20250108.sha256

第二层:格式有效性校验。检查备份文件是否符合预期格式。对于 XtraBackup,可以用 --validate 选项;对于 pg_dump 的自定义格式,可以用 pg_restore --list 检查目录结构。

# XtraBackup 验证
xtrabackup --validate --target-dir=/backup/full_20250108

# pg_dump 自定义格式验证
pg_restore --list /backup/mydb_20250108.dump > /dev/null
echo "pg_restore --list exit code: $?"

第三层:恢复可行性验证。将备份恢复到一个独立的测试实例,确认数据库能正常启动。

第四层:数据正确性验证。恢复后检查关键表的行数、校验和或业务逻辑是否正确。这是最可靠但也最耗时的验证方式。

7.2 自动化恢复测试

生产环境中应该定期(建议至少每周一次)执行自动化恢复测试。以下是一个 MySQL XtraBackup 恢复测试脚本的核心逻辑:

#!/bin/bash
# restore_test.sh —— XtraBackup 自动化恢复测试
# 在独立的测试实例上恢复最新备份并验证

set -euo pipefail

BACKUP_DIR="/backup/latest"
TEST_DATADIR="/var/lib/mysql-restore-test"
TEST_PORT=3307
LOG_FILE="/var/log/backup-restore-test.log"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "${LOG_FILE}"
}

cleanup() {
    # 停止测试实例
    mysqladmin --port="${TEST_PORT}" --socket=/run/mysqld/mysqld-test.sock \
        shutdown 2>/dev/null || true
    rm -rf "${TEST_DATADIR}"
}

trap cleanup EXIT

log "开始恢复测试"

# 第一步:准备数据目录
rm -rf "${TEST_DATADIR}"
mkdir -p "${TEST_DATADIR}"

# 第二步:解压并 prepare
xtrabackup --decompress --target-dir="${BACKUP_DIR}"
xtrabackup --prepare --target-dir="${BACKUP_DIR}"
xtrabackup --move-back --target-dir="${BACKUP_DIR}" --datadir="${TEST_DATADIR}"
chown -R mysql:mysql "${TEST_DATADIR}"

# 第三步:启动测试实例
mysqld_safe \
    --datadir="${TEST_DATADIR}" \
    --port="${TEST_PORT}" \
    --socket=/run/mysqld/mysqld-test.sock \
    --skip-slave-start &

# 等待启动
for i in $(seq 1 30); do
    if mysqladmin --port="${TEST_PORT}" \
        --socket=/run/mysqld/mysqld-test.sock ping 2>/dev/null; then
        break
    fi
    sleep 1
done

# 第四步:数据验证
RESULT=$(mysql --port="${TEST_PORT}" \
    --socket=/run/mysqld/mysqld-test.sock \
    -N -e "SELECT COUNT(*) FROM mydb.orders")
log "orders 表行数:${RESULT}"

if [ "${RESULT}" -gt 0 ]; then
    log "恢复测试通过"
    exit 0
else
    log "恢复测试失败:orders 表为空"
    exit 1
fi

7.3 验证清单

每次恢复测试至少应该检查以下项目:

  1. 备份文件校验和是否匹配。
  2. 恢复过程是否无报错完成。
  3. 数据库能否正常启动并接受连接。
  4. 关键表的行数是否在预期范围内。
  5. 最近写入的数据是否存在(验证备份的时效性)。
  6. 恢复所用时间是否在 RTO 要求内。

八、备份加密与压缩

8.1 压缩

备份压缩的目的是减少存储占用和传输时间。常用的压缩算法及其特点:

算法 压缩比(典型) 压缩速度 解压速度 适用场景
gzip 中(3-5x) 通用,兼容性最好
pigz 中(3-5x) 快(多线程 gzip) gzip 的并行替代
zstd 中高(3-6x) 极快 推荐的现代替代方案
lz4 低(2-3x) 极快 极快 对速度敏感的场景
xz / lzma 高(5-8x) 极慢 长期归档,不推荐日常备份

对于数据库备份,推荐使用 zstd。它在压缩比和速度之间取得了较好的平衡,且支持多线程:

# 使用 zstd 压缩备份(4 线程,压缩级别 3)
pg_basebackup --pgdata=- --wal-method=stream --checkpoint=fast \
    | zstd -T4 -3 -o /backup/base_20250108.tar.zst

# 解压
zstd -d -T4 /backup/base_20250108.tar.zst -o /backup/base_20250108.tar

8.2 加密

备份文件包含完整的业务数据,一旦泄露等同于数据库被拖库。备份加密是安全基线的一部分。

加密分为两种方式:

传输加密(Encryption in Transit):备份数据在网络传输过程中加密。pg_basebackup 通过 SSL 连接自动实现。XtraBackup 传输到远程存储时可以通过 SSH 隧道或 TLS 加密。

静态加密(Encryption at Rest):备份文件在存储介质上加密。有两种常见做法:

方式一:工具内置加密。pgBackRest 内置支持 AES-256-CBC 加密:

# pgBackRest 加密配置
[global]
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=your-encryption-passphrase

方式二:用 opensslgpg 在备份管道中加密:

# 使用 openssl 加密备份
pg_dump --format=custom --dbname=mydb \
    | openssl enc -aes-256-cbc -salt -pbkdf2 \
        -pass file:/etc/backup/encryption.key \
    > /backup/mydb_encrypted.dump

# 解密恢复
openssl enc -d -aes-256-cbc -pbkdf2 \
    -pass file:/etc/backup/encryption.key \
    -in /backup/mydb_encrypted.dump \
    | pg_restore --dbname=mydb

方式三:用 GPG 非对称加密,支持密钥管理和多接收者:

# 使用 GPG 加密
pg_dump --format=custom --dbname=mydb \
    | gpg --encrypt --recipient backup-team@example.com \
    > /backup/mydb_encrypted.dump.gpg

# 解密
gpg --decrypt /backup/mydb_encrypted.dump.gpg \
    | pg_restore --dbname=mydb

8.3 加密密钥管理

加密的难点不在加密本身,而在密钥管理。几个原则:


九、3-2-1 备份规则与最佳实践

9.1 3-2-1 规则

3-2-1 规则是备份领域最广泛引用的基本准则。它的含义是:

3-2-1 规则示意:

┌────────────────────────────────────────────────────────────────┐
│                         3 份副本                                │
│                                                                │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐  │
│  │  生产数据库    │  │  本地备份      │  │  异地备份(S3)       │  │
│  │  (副本 1)    │  │  (副本 2)    │  │  (副本 3)           │  │
│  │              │  │              │  │                      │  │
│  │  本地 SSD    │  │  本地 HDD    │  │  另一区域的对象存储    │  │
│  └──────────────┘  └──────────────┘  └──────────────────────┘  │
│       介质 1             介质 2              介质 2              │
│     (同城)           (同城)             (异地)             │
│                                                                │
│  2 种介质 ✓     1 份异地 ✓                                      │
└────────────────────────────────────────────────────────────────┘

3-2-1 规则的核心逻辑是降低关联故障的概率。同一台服务器上的生产数据和备份可能同时被磁盘阵列故障摧毁;同一个机房的数据可能同时被火灾或断电影响。只有物理隔离和介质隔离才能真正降低同时丢失所有副本的概率。

9.2 扩展:3-2-1-1-0 规则

在 3-2-1 的基础上,一些组织提出了 3-2-1-1-0 的扩展规则:

9.3 备份保留策略

备份不可能无限保留,需要定义保留策略。常见的策略是 GFS(Grandfather-Father-Son,祖父-父-子):

具体的保留时长取决于业务合规要求和存储成本。金融行业通常要求保留 7 年以上的备份;一般互联网业务保留 30-90 天可能就够了。

9.4 最佳实践清单

  1. 备份与生产环境隔离:备份存储使用独立的账号、独立的网络。生产环境的管理员凭证不应该能直接删除备份。
  2. 备份脚本必须检查退出码:任何备份命令执行后必须检查返回值,非零退出码必须触发告警。GitLab 事故的直接原因之一就是脚本没有检查 pg_dump 的退出码。
  3. 定期恢复演练:至少每季度做一次完整的恢复演练,验证从备份恢复到可用状态的全流程。记录恢复耗时,确认 RTO 是否达标。
  4. 备份文件加密:参见第八节。
  5. 使用不可变存储:对云存储上的备份启用 Object Lock 或等效功能,防止备份被恶意或意外删除。
  6. 监控备份的时效性:不仅监控备份任务是否成功,还要监控最近一次成功备份的时间。如果距离上次成功备份超过预期间隔(如 24 小时 + 缓冲),立即告警。
  7. 文档化恢复流程:恢复流程不能只存在于某个 DBA 的脑子里。写成 runbook,放在团队共享文档中,定期更新。

十、备份监控与告警

10.1 需要监控的指标

备份监控应覆盖以下指标:

指标 含义 告警条件示例
备份任务状态 最近一次备份任务是成功还是失败 任务失败立即告警
最近一次成功备份时间 距离现在多久 超过 RPO 的 2 倍时告警
备份文件大小 本次备份文件的大小 与前一次相比偏差超过 30% 时告警
备份耗时 备份任务执行了多长时间 超过历史平均值的 2 倍时告警
备份存储剩余空间 备份目标存储的可用空间 可用空间低于 20% 时告警
WAL 归档延迟 最新归档的 WAL 与最新生成的 WAL 之间的差距 延迟超过 N 个 WAL 段时告警
恢复测试结果 最近一次恢复测试是否通过 失败立即告警

10.2 PostgreSQL WAL 归档监控

PostgreSQL 提供了系统视图来监控 WAL 归档状态:

-- 查看归档状态
SELECT * FROM pg_stat_archiver;

-- 关键字段:
-- archived_count:已成功归档的 WAL 文件数
-- failed_count:归档失败的次数
-- last_archived_wal:最后一个成功归档的 WAL 文件名
-- last_archived_time:最后一次成功归档的时间
-- last_failed_wal:最后一个归档失败的 WAL 文件名
-- last_failed_time:最后一次归档失败的时间

基于这些指标,可以构建 Prometheus 监控查询:

# Prometheus 告警规则示例(需要配合 postgres_exporter)
groups:
  - name: backup_alerts
    rules:
      - alert: WALArchiveFailing
        expr: pg_stat_archiver_failed_count > 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "PostgreSQL WAL 归档失败"
          description: "WAL 归档失败次数:{{ $value }},最后失败的文件:{{ $labels.last_failed_wal }}"

      - alert: WALArchiveStale
        expr: time() - pg_stat_archiver_last_archive_time > 3600
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "PostgreSQL WAL 归档停滞"
          description: "距离上次成功归档已超过 1 小时"

10.3 备份任务监控脚本

以下脚本展示了一个基于文件时间戳的备份时效性检查逻辑:

#!/bin/bash
# check_backup_freshness.sh —— 检查备份文件的时效性
# 通过 Prometheus node_exporter 的 textfile collector 暴露指标

BACKUP_DIR="/backup"
METRICS_FILE="/var/lib/node_exporter/textfile/backup_freshness.prom"
MAX_AGE_SECONDS=90000  # 25 小时(给 24 小时备份周期留 1 小时缓冲)

# 查找最新的备份文件
LATEST_BACKUP=$(find "${BACKUP_DIR}" -name "*.dump" -o -name "*.tar.zst" \
    | sort -t_ -k2 -r | head -1)

if [ -z "${LATEST_BACKUP}" ]; then
    echo "# HELP backup_last_success_timestamp_seconds 最近一次成功备份的 Unix 时间戳" \
        > "${METRICS_FILE}"
    echo "backup_last_success_timestamp_seconds 0" >> "${METRICS_FILE}"
    echo "backup_freshness_ok 0" >> "${METRICS_FILE}"
    exit 0
fi

BACKUP_TIMESTAMP=$(stat -c %Y "${LATEST_BACKUP}")
CURRENT_TIME=$(date +%s)
AGE=$((CURRENT_TIME - BACKUP_TIMESTAMP))

{
    echo "# HELP backup_last_success_timestamp_seconds 最近一次成功备份的 Unix 时间戳"
    echo "backup_last_success_timestamp_seconds ${BACKUP_TIMESTAMP}"
    echo "# HELP backup_age_seconds 最近一次备份距今的秒数"
    echo "backup_age_seconds ${AGE}"
    echo "# HELP backup_freshness_ok 备份时效性是否正常(1=正常,0=过期)"
    if [ "${AGE}" -lt "${MAX_AGE_SECONDS}" ]; then
        echo "backup_freshness_ok 1"
    else
        echo "backup_freshness_ok 0"
    fi
} > "${METRICS_FILE}"

10.4 告警分级

备份告警建议按以下分级处理:

告警必须发送到实际有人值守的通道——如果备份告警被静默了或发到一个没人看的邮件列表,等于没有告警。


十一、参考文献

官方文档

  1. PostgreSQL 16 Documentation. Chapter 26: Backup and Restore. https://www.postgresql.org/docs/16/backup.html. PostgreSQL 官方的备份与恢复文档,覆盖 SQL dump、文件系统级备份和连续归档。

  2. PostgreSQL 16 Documentation. Chapter 27: Recovery Configuration. https://www.postgresql.org/docs/16/runtime-config-wal.html. WAL 配置参数的官方说明。

  3. MySQL 8.0 Reference Manual. Chapter 9: Backup and Recovery. https://dev.mysql.com/doc/refman/8.0/en/backup-and-recovery.html. MySQL 官方的备份与恢复文档。

  4. MySQL 8.0 Reference Manual. mysqldump — A Database Backup Program. https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html. mysqldump 的完整参数说明。

  5. Percona XtraBackup 8.0 Documentation. https://docs.percona.com/percona-xtrabackup/8.0/. XtraBackup 的官方文档,覆盖全量/增量备份、prepare 和恢复流程。

工具文档

  1. pgBackRest Documentation. https://pgbackrest.org/. pgBackRest 的官方文档,覆盖配置、使用和架构。

  2. Barman Documentation. https://docs.pgbarman.org/. Barman 的官方使用文档。

  3. WAL-G Documentation. https://github.com/wal-g/wal-g. WAL-G 项目的 GitHub 仓库和文档。

事故复盘

  1. GitLab. GitLab.com Database Incident — 2017-01-31. https://about.gitlab.com/blog/2017/02/01/gitlab-dot-com-database-incident/. GitLab 2017 年数据库事故的官方复盘报告,详细记录了五种备份机制如何同时失效。

  2. GitLab. Postmortem of database outage of January 31. https://about.gitlab.com/blog/2017/02/10/postmortem-of-database-outage-of-january-31/. 事故的完整事后分析。

书籍与论文

  1. Kleppmann, M. (2017). Designing Data-Intensive Applications. O’Reilly. 第七章”事务”讨论了持久性保证与备份的关系。

  2. Obe, R., Hsu, L. (2024). PostgreSQL: Up and Running, 4th Edition. O’Reilly. PostgreSQL 管理和备份的实践指南。

  3. Schwartz, B., et al. (2012). High Performance MySQL, 3rd Edition. O’Reilly. MySQL 备份策略的深入讨论见第十五章。

行业规范

  1. NIST SP 800-184. Guide for Cybersecurity Event Recovery. https://csrc.nist.gov/publications/detail/sp/800-184/final. NIST 关于网络安全事件恢复的指南,包含备份策略的安全要求。

  2. ISO 22301:2019. Security and resilience — Business continuity management systems — Requirements. 业务连续性管理的国际标准,定义了 RPO 和 RTO 的框架。


上一篇: 存储故障模式 下一篇: 灾难恢复设计

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-22 · db / storage

数据库内核实验索引

汇总本站数据库内核与存储引擎实验文章,重点覆盖从零实现 LSM-Tree 及其工程权衡。

2026-04-22 · storage

存储工程索引

汇总本站存储工程系列文章,覆盖 HDD、SSD、NVMe、持久内存、索引结构、压缩、分布式存储与对象存储。

2025-10-18 · storage

【存储工程】云块存储架构

深入剖析云块存储——分布式块存储架构原理、AWS EBS与阿里云ESSD架构分析、云盘性能规格解读、性能测试方法与选型成本优化

2025-10-19 · storage

【存储工程】云对象存储内部架构

深入剖析云对象存储——S3的11个9持久性实现、元数据-索引-存储三层架构、跨AZ复制策略、存储类别实现差异与成本模型分析


By .