第 7 章:快照管理
第 7 章:快照管理
时间机器:让文件系统拥有"后悔药"
7.1 快照基础
7.1.1 快照原理
Bcachefs 快照是 CoW 的自然延伸:
创建前:
┌─────────────────────────────────────────┐
│ B-Tree: /data/file.txt → Block A │
│ 引用计数: Block A = 1 │
└─────────────────────────────────────────┘
创建快照后:
┌─────────────────────────────────────────┐
│ B-Tree: /data/file.txt → Block A │
│ B-Tree: /snap/file.txt → Block A │
│ 引用计数: Block A = 2 │
│ 空间占用: 0(共享数据块) │
└─────────────────────────────────────────┘
修改 /data/file.txt 后:
┌─────────────────────────────────────────┐
│ B-Tree: /data/file.txt → Block B (新) │
│ B-Tree: /snap/file.txt → Block A (旧) │
│ 引用计数: Block A = 1, Block B = 1 │
│ 空间占用: 新数据的大小 │
└─────────────────────────────────────────┘
7.1.2 快照 vs 备份
| 维度 | 快照 | 传统备份 |
|---|---|---|
| 创建速度 | 毫秒级(O(1)) | 分钟-小时级 |
| 空间开销 | 几乎为零(初始) | 完整副本 |
| 数据位置 | 同一文件系统 | 不同设备/位置 |
| 灾难恢复 | 不能抵御设备故障 | 可以 |
| 性能影响 | 极小 | 备份时影响 I/O |
| 恢复速度 | 即时 | 取决于备份大小 |
7.1.3 快照类型
Bcachefs 快照类型:
1. 子卷快照 (Subvolume Snapshot)
- 对整个子卷创建快照
- 最常用的方式
2. 文件系统快照 (Filesystem Snapshot)
- 对整个文件系统创建快照
- 需要文件系统级的保护
3. 只读快照 (Read-only Snapshot)
- 不可修改
- 推荐用于备份
4. 可写快照 (Writable Snapshot)
- 可以修改
- 适用于测试/开发
7.2 创建快照
7.2.1 创建只读快照
# 创建只读快照(推荐)
sudo bcachefs subvolume snapshot -r /mnt/data /mnt/snapshots/snap-$(date +%Y%m%d)
# 查看快照
ls -la /mnt/snapshots/
# 快照看起来像普通目录,但实际是只读的
7.2.2 创建可写快照
# 创建可写快照(可以修改)
sudo bcachefs subvolume snapshot /mnt/data /mnt/snapshots/writable-snap
# 可以直接修改快照中的文件
echo "修改" > /mnt/snapshots/writable-snap/test.txt
7.2.3 对子卷创建快照
# 先创建子卷
sudo bcachefs subvolume create /mnt/data/projects
# 对子卷创建快照
sudo bcachefs subvolume snapshot -r /mnt/data/projects /mnt/snapshots/projects-$(date +%Y%m%d)
# 查看子卷列表和快照
sudo bcachefs subvolume list /mnt/data
7.2.4 带标签的快照
# 使用时间戳作为快照名
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
sudo bcachefs subvolume snapshot -r /mnt/data /mnt/snapshots/snap-${TIMESTAMP}
# 使用描述性名称
sudo bcachefs subvolume snapshot -r /mnt/data /mnt/snapshots/before-upgrade
sudo bcachefs subvolume snapshot -r /mnt/data /mnt/snapshots/after-upgrade
7.2.5 创建快照的最佳实践
#!/bin/bash
# create-snapshot.sh
set -e
MOUNT="/mnt/data"
SNAPSHOT_DIR="/mnt/snapshots"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
SNAPSHOT_NAME="snap-${TIMESTAMP}"
echo "=== 创建快照 ==="
echo "源: $MOUNT"
echo "快照: $SNAPSHOT_DIR/$SNAPSHOT_NAME"
echo ""
# 1. 刷新文件系统缓冲
sync
# 2. 如果是数据库,刷新数据库缓冲
# 例如 PostgreSQL:
# psql -c "CHECKPOINT"
# 例如 MySQL:
# mysql -e "FLUSH TABLES WITH READ LOCK;"
# 3. 创建只读快照
sudo bcachefs subvolume snapshot -r "$MOUNT" "$SNAPSHOT_DIR/$SNAPSHOT_NAME"
# 4. 验证快照
if [ -d "$SNAPSHOT_DIR/$SNAPSHOT_NAME" ]; then
echo "✅ 快照创建成功"
ls -la "$SNAPSHOT_DIR/$SNAPSHOT_NAME"
else
echo "❌ 快照创建失败"
exit 1
fi
7.3 管理快照
7.3.1 列出快照
# 列出所有子卷(包括快照)
sudo bcachefs subvolume list /mnt/data
# 输出示例:
# ID 256: /projects
# ID 257: /snapshots/snap-20250101
# ID 258: /snapshots/snap-20250102
# ID 259: /snapshots/snap-20250103
7.3.2 查看快照信息
# 查看快照占用的空间
sudo bcachefs fs usage /mnt/data
# 比较快照与当前数据的差异
diff -rq /mnt/data/ /mnt/snapshots/snap-20250101/ 2>/dev/null | head -20
# 统计快照中文件数量
find /mnt/snapshots/snap-20250101 -type f | wc -l
7.3.3 删除快照
# 删除单个快照
sudo bcachefs subvolume delete /mnt/snapshots/snap-20250101
# 批量删除旧快照
for snap in /mnt/snapshots/snap-2024*; do
echo "删除: $snap"
sudo bcachefs subvolume delete "$snap"
done
# 删除所有快照(谨慎操作!)
sudo bcachefs subvolume delete --recursive /mnt/snapshots/
7.3.4 重命名快照
# Bcachefs 不直接支持重命名子卷/快照
# 需要通过复制实现
# 创建新名称的快照
sudo bcachefs subvolume snapshot -r /mnt/data /mnt/snapshots/new-name
# 删除旧快照
sudo bcachefs subvolume delete /mnt/snapshots/old-name
7.4 从快照恢复
7.4.1 直接复制恢复
# 从快照中恢复单个文件
cp /mnt/snapshots/snap-20250101/important.txt /mnt/data/
# 恢复整个目录
cp -a /mnt/snapshots/snap-20250101/projects/ /mnt/data/projects-restored/
# 使用 rsync 恢复(保留权限)
sudo rsync -av /mnt/snapshots/snap-20250101/ /mnt/data/
7.4.2 完整恢复流程
#!/bin/bash
# restore-from-snapshot.sh
set -e
SNAPSHOT="/mnt/snapshots/snap-20250101"
TARGET="/mnt/data"
echo "=== 从快照恢复 ==="
echo "快照: $SNAPSHOT"
echo "目标: $TARGET"
echo ""
# 确认
read -p "这会覆盖当前数据!确认继续?(y/N) " confirm
if [ "$confirm" != "y" ]; then
echo "取消操作"
exit 0
fi
# 1. 创建当前状态的快照(以防万一)
echo "创建当前状态快照..."
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
sudo bcachefs subvolume snapshot -r "$TARGET" "/mnt/snapshots/before-restore-${TIMESTAMP}"
# 2. 清空当前数据
echo "清空当前数据..."
sudo rm -rf "${TARGET:?}"/*
# 3. 从快照恢复
echo "从快照恢复..."
sudo rsync -av "$SNAPSHOT/" "$TARGET/"
# 4. 验证
echo ""
echo "=== 恢复完成 ==="
echo "已恢复的文件数量:"
find "$TARGET" -type f | wc -l
7.4.3 部分恢复
# 只恢复特定文件
cp /mnt/snapshots/snap-20250101/documents/report.pdf /mnt/data/documents/
# 只恢复特定类型的文件
find /mnt/snapshots/snap-20250101 -name "*.conf" -exec cp {} /mnt/data/config/ \;
# 恢复到不同位置
cp -r /mnt/snapshots/snap-20250101/projects/ /mnt/data/old-projects/
7.5 自动化快照策略
7.5.1 定时快照脚本
#!/bin/bash
# auto-snapshot.sh
set -e
MOUNT="/mnt/data"
SNAPSHOT_DIR="/mnt/snapshots"
DAILY_KEEP=7
WEEKLY_KEEP=4
MONTHLY_KEEP=6
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
DAY_OF_WEEK=$(date +%u)
DAY_OF_MONTH=$(date +%d)
# 创建每日快照
create_daily() {
local name="daily-${TIMESTAMP}"
echo "创建每日快照: $name"
sudo bcachefs subvolume snapshot -r "$MOUNT" "$SNAPSHOT_DIR/$name"
}
# 创建每周快照
create_weekly() {
if [ "$DAY_OF_WEEK" = "7" ]; then # 周日
local name="weekly-${TIMESTAMP}"
echo "创建每周快照: $name"
sudo bcachefs subvolume snapshot -r "$MOUNT" "$SNAPSHOT_DIR/$name"
fi
}
# 创建每月快照
create_monthly() {
if [ "$DAY_OF_MONTH" = "01" ]; then # 每月 1 号
local name="monthly-${TIMESTAMP}"
echo "创建每月快照: $name"
sudo bcachefs subvolume snapshot -r "$MOUNT" "$SNAPSHOT_DIR/$name"
fi
}
# 清理旧快照
cleanup_old() {
echo "清理旧快照..."
# 保留最近 N 个每日快照
ls -dt "$SNAPSHOT_DIR"/daily-* 2>/dev/null | tail -n +$((DAILY_KEEP+1)) | while read snap; do
echo "删除: $snap"
sudo bcachefs subvolume delete "$snap"
done
# 保留最近 N 个每周快照
ls -dt "$SNAPSHOT_DIR"/weekly-* 2>/dev/null | tail -n +$((WEEKLY_KEEP+1)) | while read snap; do
echo "删除: $snap"
sudo bcachefs subvolume delete "$snap"
done
# 保留最近 N 个月度快照
ls -dt "$SNAPSHOT_DIR"/monthly-* 2>/dev/null | tail -n +$((MONTHLY_KEEP+1)) | while read snap; do
echo "删除: $snap"
sudo bcachefs subvolume delete "$snap"
done
}
# 执行
echo "=== 自动快照 $(date) ==="
create_daily
create_weekly
create_monthly
cleanup_old
echo ""
echo "=== 完成 ==="
echo "当前快照数量:"
ls "$SNAPSHOT_DIR" | wc -l
7.5.2 配置 Crontab
# 编辑 crontab
sudo crontab -e
# 添加以下行:
# 每天凌晨 2 点创建快照
0 2 * * * /usr/local/bin/auto-snapshot.sh >> /var/log/snapshot.log 2>&1
# 每 6 小时创建快照(适合频繁变化的数据)
0 */6 * * * /usr/local/bin/auto-snapshot.sh >> /var/log/snapshot.log 2>&1
7.5.3 Systemd Timer 配置
# /etc/systemd/system/bcachefs-snapshot.service
[Unit]
Description=Bcachefs automatic snapshot
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/auto-snapshot.sh
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/bcachefs-snapshot.timer
[Unit]
Description=Run Bcachefs snapshot every 6 hours
[Timer]
OnCalendar=*-*-* 00/6:00:00
Persistent=true
[Install]
WantedBy=timers.target
# 启用定时器
sudo systemctl enable --now bcachefs-snapshot.timer
# 检查状态
sudo systemctl status bcachefs-snapshot.timer
sudo journalctl -u bcachefs-snapshot.service
7.6 快照与数据库一致性
7.6.1 数据库一致性挑战
问题: 数据库可能有脏页在内存中
┌─────────────────────────────────────────┐
│ 内存中的脏页: [A', B', C'] │
│ 磁盘上的数据: [A, B, C] │
│ │
│ 如果直接创建快照: │
│ 快照包含: [A, B, C] │
│ 实际应该是: [A', B', C'] │
│ → 不一致! │
└─────────────────────────────────────────┘
解决方案: 创建快照前刷新数据库缓冲
7.6.2 PostgreSQL
#!/bin/bash
# snapshot-postgres.sh
# 1. 暂停写入(可选)
psql -c "SELECT pg_start_backup('bcachefs-snapshot', true);"
# 2. 创建快照
sudo bcachefs subvolume snapshot -r /var/lib/postgresql /mnt/snapshots/pg-$(date +%Y%m%d)
# 3. 恢复写入
psql -c "SELECT pg_stop_backup();"
7.6.3 MySQL / MariaDB
#!/bin/bash
# snapshot-mysql.sh
# 1. 刷新并锁定表
mysql -e "FLUSH TABLES WITH READ LOCK;"
# 2. 创建快照
sudo bcachefs subvolume snapshot -r /var/lib/mysql /mnt/snapshots/mysql-$(date +%Y%m%d)
# 3. 解锁
mysql -e "UNLOCK TABLES;"
7.6.4 SQLite
#!/bin/bash
# snapshot-sqlite.sh
# SQLite 使用文件锁,只需确保没有写入
# 1. 检查是否有写入进程
lsof /path/to/database.db
# 2. 创建快照
sudo bcachefs subvolume snapshot -r /path/to/data /mnt/snapshots/sqlite-$(date +%Y%m%d)
7.7 快照监控
7.7.1 监控快照数量
#!/bin/bash
# monitor-snapshots.sh
SNAPSHOT_DIR="/mnt/snapshots"
MAX_SNAPSHOTS=50
count=$(ls "$SNAPSHOT_DIR" | wc -l)
echo "当前快照数量: $count"
if [ "$count" -gt "$MAX_SNAPSHOTS" ]; then
echo "⚠️ 警告: 快照数量超过阈值 ($MAX_SNAPSHOTS)"
# 发送告警
fi
7.7.2 监控快照空间
#!/bin/bash
# monitor-snapshot-space.sh
MOUNT="/mnt/data"
# 查看文件系统使用情况
sudo bcachefs fs usage "$MOUNT"
# 快照本身不占用额外空间(初始时)
# 但修改后的 CoW 数据会占用空间
7.7.3 Prometheus 指标
# 如果使用 node_exporter,可以自定义指标
# /etc/node_exporter/textfile_collector/bcachefs_snapshots.prom
#!/bin/bash
SNAPSHOT_COUNT=$(ls /mnt/snapshots | wc -l)
echo "bcachefs_snapshot_count $SNAPSHOT_COUNT"
7.8 快照性能影响
7.8.1 创建性能
# 快照创建是 O(1) 操作,几乎不消耗时间
time sudo bcachefs subvolume snapshot -r /mnt/data /mnt/snapshots/test-snap
# 通常 < 0.01 秒
7.8.2 写入性能影响
大量快照时的写入开销:
1 个快照:
写入 Block A → 只需更新 1 个引用 → 开销小
N 个快照:
写入 Block A → 需要检查 N 个引用 → 开销增加
建议:
- 保留不超过 50-100 个快照
- 定期清理旧快照
7.8.3 读取性能影响
# 快照不影响正常读取性能
# 读取快照文件与读取普通文件性能相同
# 但大量快照可能影响元数据查询
# 建议使用子卷隔离快照
7.9 快照与子卷的关系
7.9.1 使用子卷组织快照
# 创建数据子卷
sudo bcachefs subvolume create /mnt/data/documents
sudo bcachefs subvolume create /mnt/data/projects
# 为每个子卷创建独立的快照
sudo bcachefs subvolume snapshot -r /mnt/data/documents /mnt/snapshots/docs-$(date +%Y%m%d)
sudo bcachefs subvolume snapshot -r /mnt/data/projects /mnt/snapshots/proj-$(date +%Y%m%d)
# 优势: 可以独立管理不同数据集的快照
7.9.2 子卷快照 vs 根快照
子卷快照 (推荐):
/mnt/data/
├── documents/ (子卷)
│ └── ...数据...
└── projects/ (子卷)
└── ...数据...
快照:
/mnt/snapshots/
├── docs-20250101/ ← 只包含 documents
└── proj-20250101/ ← 只包含 projects
根快照:
对整个 /mnt/data 创建快照
包含所有子卷 → 更大,管理更复杂
7.10 快照灾难恢复
7.10.1 快照不能替代备份
快照在同一文件系统上:
设备故障 → 快照也丢失!
正确的备份策略:
快照(本地保护)+ 远程备份(灾难恢复)
推荐架构:
┌─────────────────────────────────────────┐
│ 本地存储 │
│ ├── 数据 │
│ ├── 每日快照 │
│ └── 周/月快照 │
├─────────────────────────────────────────┤
│ 远程备份 │
│ ├── rsync 到远程服务器 │
│ ├── 磁带备份 │
│ └── 云存储 │
└─────────────────────────────────────────┘
7.10.2 从快照创建远程备份
#!/bin/bash
# snapshot-to-remote.sh
SNAPSHOT_DIR="/mnt/snapshots"
REMOTE_HOST="backup-server"
REMOTE_PATH="/backup/data"
# 获取最新快照
LATEST_SNAP=$(ls -dt "$SNAPSHOT_DIR"/daily-* | head -1)
echo "同步快照到远程: $LATEST_SNAP"
# 使用 rsync 同步
rsync -avz --delete \
"$LATEST_SNAP/" \
"${REMOTE_HOST}:${REMOTE_PATH}/"
echo "同步完成"
7.11 本章小结
| 操作 | 命令 | 说明 |
|---|---|---|
| 创建只读快照 | bcachefs subvolume snapshot -r src dst | 推荐方式 |
| 创建可写快照 | bcachefs subvolume snapshot src dst | 可修改 |
| 列出快照 | bcachefs subvolume list /mnt | 查看所有子卷/快照 |
| 删除快照 | bcachefs subvolume delete /path | 释放空间 |
| 恢复数据 | cp /snap/file /data/ | 直接复制 |
最佳实践:
- 使用只读快照保护数据
- 通过子卷隔离不同数据集
- 自动化快照策略
- 定期清理旧快照
- 快照不能替代远程备份
扩展阅读
上一章: ← 第 6 章:多设备与分层存储 | 下一章: 第 8 章:压缩策略 →