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 知识体系总览
下面这张图,是我对本章知识点的总结。你可以把它当作一个「快照与备份」的决策树。
嗯,这张图基本把本章的核心脉络串起来了。从文件系统快照到数据库快照,再到应用层备份和生命周期管理,每一层都有各自的侧重点。你想想看,如果只做文件系统快照,数据库事务一致性怎么保证?如果只做数据库备份,应用配置丢了怎么办?所以,分层设计、组合使用,才是靠谱的做法。