Btrfs 文件系统运维完全教程 / 第 10 章:发送与接收 (Send/Receive)
第 10 章:发送与接收 (Send/Receive)
10.1 Send/Receive 概述
10.1.1 工作原理
Btrfs Send/Receive 是基于快照的增量备份机制:
源文件系统 (生产服务器) 目标文件系统 (备份服务器)
┌──────────────────┐ ┌──────────────────┐
│ 子卷 @data │ │ 子卷 @data │
│ ├── 快照 1 (ro)│── 全量发送 ─→ │ ├── 快照 1 │
│ ├── 快照 2 (ro)│── 增量发送 ─→ │ ├── 快照 2 │
│ └── 快照 3 (ro)│── 增量发送 ─→ │ └── 快照 3 │
└──────────────────┘ └──────────────────┘
10.1.2 特性
| 特性 | 说明 |
|---|
| 增量传输 | 只传输两个快照之间的差异数据 |
| 原子性 | 接收方要么完整收到快照,要么失败 |
| 压缩数据 | 支持直接传输压缩数据 |
| 流式传输 | 输出为字节流,可通过管道传输 |
| 跨网络 | 配合 SSH/rsync 实现远程备份 |
| 只读快照 | 发送方必须是只读快照 |
10.1.3 增量传输效率
快照 1: 10GB 数据
快照 2: 新增 100MB 数据
全量发送快照 2: 传输 10.1GB
增量发送快照 2: 传输 ~100MB ← 效率提升 100 倍!
10.2 本地 Send/Receive
10.2.1 全量发送
# 创建只读快照
sudo btrfs subvolume snapshot -r /mnt/@data /mnt/@snapshots/data-full
# 全量发送到另一个 Btrfs 文件系统
sudo btrfs send /mnt/@snapshots/data-full | sudo btrfs receive /backup/
# 发送到文件
sudo btrfs send -f /backup/data-full.send /mnt/@snapshots/data-full
10.2.2 增量发送
# 创建新的只读快照
sudo btrfs subvolume snapshot -r /mnt/@data /mnt/@snapshots/data-incr
# 增量发送(-p 指定父快照)
sudo btrfs send -p /mnt/@snapshots/data-full /mnt/@snapshots/data-incr | \
sudo btrfs receive /backup/
# 增量发送到文件
sudo btrfs send -p /mnt/@snapshots/data-full /mnt/@snapshots/data-incr \
-f /backup/data-incr.send
10.2.3 多层级增量
# 快照链:snap1 → snap2 → snap3 → snap4
# 可以基于任何相邻快照做增量
# snap1 → snap2(增量基于 snap1)
sudo btrfs send -p /snap1 /snap2 | sudo btrfs receive /backup/
# snap2 → snap3(增量基于 snap2)
sudo btrfs send -p /snap2 /snap3 | sudo btrfs receive /backup/
# snap1 → snap4(大增量,但可以)
sudo btrfs send -p /snap1 /snap4 | sudo btrfs receive /backup/
10.3 远程 Send/Receive
10.3.1 通过 SSH 传输
# 全量发送到远程服务器
sudo btrfs send /mnt/@snapshots/data-full | \
ssh user@backup-server "sudo btrfs receive /backup/"
# 增量发送到远程服务器
sudo btrfs send -p /mnt/@snapshots/data-old /mnt/@snapshots/data-new | \
ssh user@backup-server "sudo btrfs receive /backup/"
# 带进度监控
sudo btrfs send /mnt/@snapshots/data-full | \
pv | ssh user@backup-server "sudo btrfs receive /backup/"
# 使用压缩数据传输(节省带宽)
sudo btrfs send --compressed-data /mnt/@snapshots/data-full | \
ssh user@backup-server "sudo btrfs receive /backup/"
10.3.2 通过 netcat 传输(高速局域网)
# 接收端(备份服务器)
nc -l -p 9999 | sudo btrfs receive /backup/
# 发送端(生产服务器)
sudo btrfs send /mnt/@snapshots/data-full | nc backup-server 9999
10.3.3 传输安全与效率
| 方法 | 安全性 | 速度 | 适用场景 |
|---|
| SSH | 高 | 中等(加密开销) | 远程/公网 |
| netcat + VPN | 高 | 快 | 局域网 |
| netcat | 低 | 最快 | 内网/可信环境 |
| 文件 + rsync | 高 | 中等 | 离线备份 |
10.4 自动化备份脚本
10.4.1 完整备份脚本
#!/bin/bash
# btrfs-backup.sh - Btrfs 自动化增量备份脚本
set -euo pipefail
# 配置
SOURCE_MNT="/mnt/data"
BACKUP_HOST="backup-server"
BACKUP_USER="backup"
BACKUP_PATH="/backup/data"
SNAPSHOT_DIR="/mnt/@snapshots"
SUBVOLUMES=("@home" "@var" "@config")
KEEP_SNAPSHOTS=10
LOG_FILE="/var/log/btrfs-backup.log"
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
# 获取最新快照
get_latest_snapshot() {
local subvol="$1"
sudo btrfs subvolume list -s "$SOURCE_MNT" | \
grep "path ${SNAPSHOT_DIR##*/}/${subvol}-" | \
sort -t'-' -k2 | tail -1 | awk '{print $NF}'
}
# 创建只读快照
create_snapshot() {
local subvol="$1"
local timestamp=$(date '+%Y%m%d-%H%M%S')
local snap_name="${subvol}-${timestamp}"
local snap_path="${SNAPSHOT_DIR}/${snap_name}"
log "Creating snapshot: $snap_name"
if sudo btrfs subvolume snapshot -r "${SOURCE_MNT}/${subvol}" "$snap_path" 2>>"$LOG_FILE"; then
echo "$snap_name"
else
log "ERROR: Failed to create snapshot $snap_name"
return 1
fi
}
# 发送增量备份
send_backup() {
local subvol="$1"
local new_snap="$2"
local parent_snap="$3"
if [[ -n "$parent_snap" ]]; then
log "Sending incremental: $parent_snap → $new_snap"
sudo btrfs send -p "${SNAPSHOT_DIR}/${parent_snap}" "${SNAPSHOT_DIR}/${new_snap}" | \
ssh "${BACKUP_USER}@${BACKUP_HOST}" "sudo btrfs receive ${BACKUP_PATH}/" 2>>"$LOG_FILE"
else
log "Sending full: $new_snap"
sudo btrfs send "${SNAPSHOT_DIR}/${new_snap}" | \
ssh "${BACKUP_USER}@${BACKUP_HOST}" "sudo btrfs receive ${BACKUP_PATH}/" 2>>"$LOG_FILE"
fi
}
# 清理旧快照
cleanup_snapshots() {
local subvol="$1"
local snapshots=($(sudo btrfs subvolume list -s "$SOURCE_MNT" | \
grep "path ${SNAPSHOT_DIR##*/}/${subvol}-" | \
sort -t'-' -k2 | awk '{print $NF}'))
local count=${#snapshots[@]}
if (( count > KEEP_SNAPSHOTS )); then
local to_delete=$(( count - KEEP_SNAPSHOTS ))
log "Cleaning up $to_delete old snapshots for $subvol"
for (( i=0; i<to_delete; i++ )); do
local snap="${snapshots[$i]}"
log "Deleting: $snap"
sudo btrfs subvolume delete "${SOURCE_MNT}/${snap}" 2>>"$LOG_FILE" || true
done
fi
}
# 主流程
main() {
log "=== Starting Btrfs backup ==="
for subvol in "${SUBVOLUMES[@]}"; do
log "--- Processing $subvol ---"
# 获取最新的本地快照
local latest=$(get_latest_snapshot "$subvol")
# 创建新快照
local new_snap=$(create_snapshot "$subvol")
# 发送备份
send_backup "$subvol" "$new_snap" "$latest"
# 清理旧快照
cleanup_snapshots "$subvol"
done
log "=== Backup completed ==="
}
main "$@"
10.4.2 配置 systemd 定时备份
# /etc/systemd/system/btrfs-backup.timer
[Unit]
Description=Btrfs Backup Timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=600
[Install]
WantedBy=timers.target
# /etc/systemd/system/btrfs-backup.service
[Unit]
Description=Btrfs Backup Service
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/btrfs-backup.sh
User=root
# 启用定时备份
sudo systemctl enable --now btrfs-backup.timer
10.5 备份验证
10.5.1 验证备份完整性
# 1. 检查备份服务器上的快照
ssh backup-server "sudo btrfs subvolume list /backup"
# 2. 比较源和目标的文件数量
SOURCE_COUNT=$(sudo find /mnt/@data -type f | wc -l)
BACKUP_COUNT=$(ssh backup-server "sudo find /backup/@data -type f | wc -l")
echo "Source: $SOURCE_COUNT files, Backup: $BACKUP_COUNT files"
# 3. 校验关键文件的哈希
sudo sha256sum /mnt/@data/important.db
ssh backup-server "sudo sha256sum /backup/@data/important.db"
10.5.2 恢复测试
# 从备份恢复(在备份服务器上)
sudo btrfs subvolume snapshot /backup/@data /restore/data-restored
# 验证恢复的数据
sudo diff -r /backup/@data /restore/data-restored
10.6 备份策略建议
10.6.1 3-2-1 备份规则
| 规则 | 说明 | Btrfs 实现 |
|---|
| 3 份数据 | 至少 3 份副本 | 1 原始 + 2 备份 |
| 2 种介质 | 使用 2 种不同存储 | 本地 + 异地 NAS |
| 1 份异地 | 至少 1 份在异地 | SSH 发送到远程 |
10.6.2 备份频率建议
| 数据类型 | 建议频率 | 保留策略 |
|---|
| 系统配置 | 每次变更 | 保留 30 个 |
| 用户数据 | 每小时 | 保留 24 小时 + 7 天 |
| 数据库 | 每 15 分钟 | 保留 100 个 |
| 归档数据 | 每天 | 保留 90 天 |
10.7 本章小结
| 操作 | 命令 |
|---|
| 全量发送 | btrfs send /snap > backup.send |
| 增量发送 | btrfs send -p /parent /snap > incr.send |
| 远程发送 | btrfs send /snap | ssh host "btrfs receive /backup/" |
| 接收 | btrfs receive /backup/ < backup.send |
| 压缩传输 | btrfs send --compressed-data /snap | ... |
关键要点:
- 增量发送只传输差异数据,效率极高
- 发送源必须是只读快照
- 父快照必须存在于接收端
- 使用
--compressed-data 节省带宽 - 定期验证备份完整性
扩展阅读