3. 快照与备份机制

快照和备份,这俩词儿经常被混着用。但在我眼里,它们完全是两码事。快照是「瞬间定格」,备份是「异地容灾」。你想想看,一个跑着核心业务的数据库,你敢只靠快照保命吗?我不敢。下面我把文件系统、数据库、应用层这几个维度的玩法拆开讲。

核心原则:快照 ≠ 备份。快照解决「误操作回滚」,备份解决「物理损坏/站点级灾难」。两者必须搭配使用。

3.1 文件系统快照原理

文件系统快照,说白了就是给磁盘拍一张「瞬间照片」。它不复制数据,而是记录元数据状态。我最早接触这玩意儿是在 LVM 上,后来用了 ZFS,才真正体会到什么叫「快照自由」。

3.1.1 LVM 快照

LVM 快照的原理是 COW(Copy-On-Write,写时复制)。创建快照时,LVM 只记录原始卷的元数据指针。当原始数据块被修改时,才把旧数据复制到快照预留的空间里。

# 创建 LVM 快照(假设卷组为 vg_data,逻辑卷为 lv_mysql)
lvcreate -L 10G -s -n mysql_snap_20250101 /dev/vg_data/lv_mysql

# 挂载快照(只读)
mkdir /mnt/snap
mount -o ro /dev/vg_data/mysql_snap_20250101 /mnt/snap

# 回滚快照
lvconvert --merge /dev/vg_data/mysql_snap_20250101

我曾经踩过的坑:LVM 快照空间是共享的。如果快照预留空间(10G)被写满,快照会自动失效,变成「无效快照」。有一次我备份前忘了检查快照使用率,结果备份到一半快照崩了,数据全乱套。所以,监控快照空间使用率是必修课。

特性 LVM 快照 ZFS 快照
实现方式 COW(写时复制) COW + 块指针重定向
性能影响 写入放大,原始卷性能下降 几乎无性能损失
空间效率 需预分配快照空间 按需使用,共享数据块
快照数量 有限(受限于预留空间) 几乎无限(仅受磁盘容量限制)
回滚速度 快(合并 COW 块) 极快(指针切换)

3.1.2 ZFS 快照

ZFS 的快照,我个人认为是目前开源文件系统里最优雅的实现。它基于事务型 COW,创建快照时只是克隆了根块的指针。你创建 1000 个快照,原始数据也只存一份。直到数据被修改,才产生增量。

# 创建 ZFS 快照
zfs snapshot tank/data@snap_20250101

# 列出快照
zfs list -t snapshot

# 回滚到快照
zfs rollback tank/data@snap_20250101

# 克隆快照(用于测试环境)
zfs clone tank/data@snap_20250101 tank/data_test

我的习惯:ZFS 快照配合 sanoid 工具做自动化快照管理。设置每小时快照保留 24 个,每天快照保留 30 个,每月快照保留 12 个。这样既能回滚到任意时间点,又不会把磁盘撑爆。

3.2 数据库快照

数据库快照和文件系统快照不太一样。文件系统只管「块」,数据库还得管「事务一致性」。你想想看,如果数据库正在写一个事务,你拍了个快照,那这个事务是完整的还是不完整的?

3.2.1 MySQL 快照(基于 LVM/ZFS)

MySQL 做快照前,必须保证数据文件处于一致状态。我常用的方法是 FLUSH TABLES WITH READ LOCK 配合 SHOW MASTER STATUS。

-- 在 MySQL 中执行
FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;
-- 记录 binlog 位置,然后创建文件系统快照
-- 快照创建完成后,解锁
UNLOCK TABLES;

注意:生产环境执行 FLUSH TABLES WITH READ LOCK 会阻塞写入。我建议在从库或低峰期操作。如果用的是 ZFS,可以用 zfs snapshot -r 配合 MySQL 的 --lock-all-tables 参数,但依然要控制窗口时间。

3.2.2 PostgreSQL 快照

PostgreSQL 天生支持在线快照,通过 pg_start_backup() 和 pg_stop_backup() 函数。它会在数据目录里创建一个备份标签文件,记录检查点位置。

-- 开始备份模式
SELECT pg_start_backup('full_backup_20250101');

-- 创建文件系统快照(LVM 或 ZFS)

-- 结束备份模式
SELECT pg_stop_backup();

PostgreSQL 这种方式的好处是:备份期间数据库仍然可读可写。坏处是:备份模式会产生额外的 WAL 日志,长时间不结束会导致 WAL 堆积。我曾经见过有人忘了执行 pg_stop_backup(),结果 WAL 把磁盘撑满了。

3.3 应用层备份策略

文件系统和数据库层面的备份,解决的是「数据」问题。但应用层备份,解决的是「状态」和「配置」问题。我见过太多人只备份数据库,结果应用挂了之后,配置文件、缓存数据、消息队列状态全丢了。

3.3.1 应用配置备份

配置文件的备份,我建议用版本控制工具(Git)管理。每次变更都提交,配合 CI/CD 自动部署。

# 备份应用配置到 Git 仓库
cd /etc/myapp
git init
git add .
git commit -m "Initial config backup"

# 设置定时任务,每小时自动提交
0 * * * * cd /etc/myapp && git add -A && git commit -m "Auto backup $(date)" || true

3.3.2 缓存与消息队列备份

Redis、RabbitMQ 这类中间件,数据都在内存里。重启就丢。我习惯的做法是:

  • Redis:开启 RDB 持久化,配合 AOF 日志。RDB 做快照,AOF 做增量。
  • RabbitMQ:使用 rabbitmqadmin 导出队列定义和绑定关系。消息数据则依赖镜像队列和持久化。
  • Elasticsearch:使用快照 API 创建索引快照,存储到 S3 或 NFS。
# Redis 手动触发 RDB 快照
redis-cli BGSAVE

# RabbitMQ 导出定义
rabbitmqadmin export rabbitmq.definitions.json

# Elasticsearch 创建快照仓库
PUT _snapshot/my_backup
{
  "type": "s3",
  "settings": {
    "bucket": "es-snapshots",
    "region": "us-east-1"
  }
}

3.4 备份的存储与生命周期

备份存哪儿?存多久?这两个问题不搞清楚,备份就是白做。我见过有人把备份和源数据放在同一个磁盘阵列上,结果阵列坏了,备份和源数据一起完蛋。

3.4.1 存储分层

层级 存储介质 用途 恢复速度 成本
L1 本地磁盘/SSD 最近 24 小时快照 秒级 高
L2 NFS/iSCSI 最近 7 天全量备份 分钟级 中
L3 对象存储(S3/MinIO) 最近 30 天增量备份 小时级 低
L4 磁带/异地归档 月度/年度归档 天级 极低

3.4.2 生命周期策略

我习惯用「3-2-1 原则」来设计生命周期:

  • 3 份副本(原始数据 + 本地备份 + 异地备份)
  • 2 种不同介质(磁盘 + 对象存储)
  • 1 份异地存储(不同机房或云区域)

具体到时间线,我一般这么定:

# 备份保留策略示例
- 每小时快照:保留 24 个(覆盖最近 1 天)
- 每天全量备份:保留 7 个(覆盖最近 1 周)
- 每周全量备份:保留 4 个(覆盖最近 1 月)
- 每月全量备份:保留 12 个(覆盖最近 1 年)
- 年度归档:永久保留(异地存储)

我的经验:自动化清理比自动化备份更重要。我见过有人备份脚本跑得飞起,但清理脚本没写,结果磁盘被备份撑爆。建议用 cron + find 或专门的备份工具(如 borg、restic)来管理生命周期。

3.5 知识体系总览

下面这张图,是我对本章知识点的总结。你可以把它当作一个「快照与备份」的决策树。

快照与备份机制知识体系 快照与备份机制知识体系 数据保护策略 文件系统快照 数据库快照 应用层备份 存储与生命周期 LVM 快照 ZFS 快照 MySQL PostgreSQL 配置备份 缓存/消息队列 存储分层 生命周期策略 LVM 快照需预分配空间 写满后自动失效 ZFS 快照几乎无性能损失 共享数据块,按需使用 数据库快照需事务一致性 FLUSH TABLES / pg_start_backup 3-2-1 备份原则 3副本 2介质 1异地 核心:快照解决误操作,备份解决物理损坏

嗯,这张图基本把本章的核心脉络串起来了。从文件系统快照到数据库快照,再到应用层备份和生命周期管理,每一层都有各自的侧重点。你想想看,如果只做文件系统快照,数据库事务一致性怎么保证?如果只做数据库备份,应用配置丢了怎么办?所以,分层设计、组合使用,才是靠谱的做法。