强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 | ...

关键要点

  1. 增量发送只传输差异数据,效率极高
  2. 发送源必须是只读快照
  3. 父快照必须存在于接收端
  4. 使用 --compressed-data 节省带宽
  5. 定期验证备份完整性

扩展阅读