备份这件事,几乎所有运维都知道要做,但真正做到"出事时能恢复"的却不多。常见的失败模式有三种:只做了全量备份、备份文件堆了一磁盘却从没验证过、需要某一周前的版本时发现早被覆盖了。备份的价值不在于"每天都跑",而在于"需要某个历史版本时,它还在且能用"。
这篇文章讲怎么用 rsync 和 tar 配合,对指定目录做自动化备份,重点是三个能力:增量(只传变化的,不重复搬全量)、压缩(省空间)、历史版本(能回到过去的某一天)。会给出可直接运行的脚本、定时任务配置、以及恢复演练方法。
文中的保留份数、增量策略、磁盘占用估算都是经验值,请按数据量和可承受的恢复时间(也就是 RPO/RTO)调整。涉及 --delete 这类会删除目标端文件的参数,我会单独说明风险和规避方式,这类参数用错会导致备份目录被清空。
一、先想清楚备份的三个问题
在敲命令之前,先把策略想明白。一个能用的备份方案要回答三个问题:备份什么、保留多久、怎么恢复。
备份什么:不是越多越好。要备份的是"丢了会痛"的东西——应用代码与配置、用户上传的数据、数据库导出文件、证书和密钥。操作系统本身、缓存目录、日志(如果不作审计用)通常不需要备,重装系统比恢复更快。把 / 整个备下来既慢又没意义。
保留多久:取决于业务和合规要求。个人项目保留 7 天可能就够;有合规要求的业务可能需要 30 天、90 天甚至更长。保留策略的核心是"平衡空间和可恢复的时间点",常见做法是保留最近 7 天的每日版本加最近 4 周的每周版本。
怎么恢复:这是最容易被忽略的一环。没演练过的备份等于没有备份。至少要定期做一次"从备份恢复到一个临时目录"的演练,确认备份文件完整、可解压、内容正确。
| 备份方式 | 空间占用 | 备份速度 | 恢复速度 | 适用 |
|---|---|---|---|---|
| 全量 tar 压缩 | 高(每次一份完整) | 慢 | 快 | 数据量小、要求简单 |
| rsync 增量同步 | 低(只存变化,硬链接复用) | 快 | 快(目录即恢复) | 数据量大、要历史版本 |
| 快照(LVM/云盘) | 中 | 极快 | 快 | 有快照能力、要快速回滚 |
| 数据库逻辑导出 | 中 | 中 | 中 | 数据库本身的备份 |
| 异地/对象存储同步 | 中 | 中(受带宽限制) | 中 | 容灾,防整机故障 |
实践中常见组合是:本地用 rsync 增量做每日版本,再定期把增量结果同步到异地或对象存储。本地负责"快速恢复",异地负责"容灾"。
二、rsync 增量备份的原理与参数
rsync 是增量备份的主力。它的"增量"体现在传输层面:默认只传输源和目标差异的部分。但要注意,单纯的 rsync 同步得到的是"当前状态",不是历史版本。要做历史版本,靠的是 --link-dest 参数配合硬链接。
原理是:每天的备份目录里,和前一天完全相同的文件用硬链接指向同一份数据,只有变化的文件才真正占用新空间。这样每个日期目录看起来都是一份"完整备份",但实际磁盘占用只比一份全量多一点。这是 rsync 做版本化备份的精髓。
关键参数:
| 参数 | 作用 | 注意 |
|---|---|---|
-a |
归档模式(等同 -rlptgoD) |
保留权限、时间、属主等 |
-v |
显示详细过程 | 排查用 |
-z |
传输压缩 | 慢网络下有用 |
--delete |
删除目标端多余文件 | 危险,慎用,见下文 |
--link-dest=DIR |
相同文件用硬链接指向 DIR | 历史版本的关键 |
--exclude |
排除文件/目录 | 如排除 cache、tmp |
--exclude-from |
从文件读排除列表 | 排除项多时好用 |
--bwlimit |
限速(KB/s) | 避免占满带宽 |
-e ssh |
通过 SSH 传输 | 远程备份必用 |
--numeric-ids |
按数字 ID 保留属主 | 跨机备份避免用户映射错乱 |
关于 --delete 要重点说明:它让目标端与源端完全一致,会删掉目标端源端没有的文件。这本身是"镜像备份"的常用行为,但如果源目录被误删,--delete 会把备份端的对应文件也删掉,等于"备份跟着一起丢"。所以备份场景里 --delete 要非常谨慎:要么不用,要么务必确认源目录不会出现"整体消失"的情况,并且备份端有独立的保留策略。
三、完整增量备份脚本
下面这份脚本用 --link-dest 实现"每日一个完整快照、共享未变数据"的增量备份,并自动清理过期版本。
#!/usr/bin/env bash
# 目录增量备份脚本(rsync + link-dest 历史版本)
# 用法:bash backup_dir.sh
set -uo pipefail
# ---- 配置区(经验值,按需调整)----
SRC="/data/www" # 要备份的源目录
DEST="/backup/www" # 备份根目录(建议挂独立盘)
KEEP=7 # 保留最近 7 个每日版本(经验值)
LOG="/var/log/backup_www.log"
EXCLUDE_FILE="/etc/backup_exclude.txt"
# 排除规则:缓存、临时、会话等无需备份的内容
cat > "$EXCLUDE_FILE" <<'EOF'
cache/
tmp/
*.sock
*.pid
node_modules/
.git/
EOF
TODAY=$(date +%F)
DEST_TODAY="$DEST/$TODAY"
# 前一天的版本目录,用于 link-dest(共享未变文件)
PREV=$(ls -1d "$DEST"/????-??-?? 2>/dev/null | sort | tail -1)
log() { echo "[$(date '+%F %T')] $*" | tee -a "$LOG"; }
mkdir -p "$DEST"
log "==== 开始备份 $SRC -> $DEST_TODAY ===="
# 组装 link-dest 参数
LINK_OPT=""
[ -n "$PREV" ] && LINK_OPT="--link-dest=$PREV"
log "基准版本: ${PREV:-无(首次全量)}"
# 执行备份:先写临时目录,成功后原子改名,避免半个备份被当成完整版本
TMP="$DEST/.incomplete-$TODAY"
rm -rf "$TMP"
rsync -a --delete \
--exclude-from="$EXCLUDE_FILE" \
$LINK_OPT \
"$SRC"/ "$TMP"/
if [ $? -eq 0 ]; then
rm -rf "$DEST_TODAY"
mv "$TMP" "$DEST_TODAY"
log "[OK] 备份完成: $DEST_TODAY"
# 记录本次占用(硬链接共享后,实际增量很小)
log "本次总占用: $(du -sh "$DEST_TODAY" 2>/dev/null | awk '{print $1}')"
else
log "[FAIL] rsync 失败,保留 $TMP 供排查"
exit 1
fi
# 清理过期版本
COUNT=$(ls -1d "$DEST"/????-??-?? 2>/dev/null | wc -l)
if [ "$COUNT" -gt "$KEEP" ]; then
DEL=$((COUNT - KEEP))
ls -1d "$DEST"/????-??-?? | sort | head -n "$DEL" | while read -r old; do
log "清理过期版本: $old"
rm -rf "$old"
done
fi
# 汇总
log "当前保留版本数: $(ls -1d "$DEST"/????-??-?? | wc -l) 总占用: $(du -sh "$DEST" | awk '{print $1}')"
log "==== 备份结束 ===="
几个设计点值得强调。先写临时目录再原子改名:如果备份过程中断,DEST 里不会出现一个"看似完整其实残缺"的日期目录——残缺的会留在 .incomplete- 前缀下,不影响正常版本。--link-dest 指向前一天:未变化的文件共享 inode,变化的文件才占新空间,所以保留 7 天不等于占 7 倍空间。排除规则独立成文件:便于维护和统一管理。
四、tar 压缩归档方案
如果更看重"把整份打包成一个可搬运的文件",tar 更合适。它适合数据量不大、或需要把归档传去异地的场景。
#!/usr/bin/env bash
# tar 压缩归档备份
set -uo pipefail
SRC="/data/www"
DEST="/backup/archives"
KEEP=7
TS=$(date +%F)
mkdir -p "$DEST"
# 压缩打包(排除缓存等)
tar -czf "$DEST/www-$TS.tar.gz" \
--exclude='cache' --exclude='tmp' --exclude='*.sock' \
-C "$(dirname "$SRC")" "$(basename "$SRC")"
# 校验归档完整性(重要!)
if tar -tzf "$DEST/www-$TS.tar.gz" >/dev/null 2>&1; then
echo "[OK] 归档校验通过: www-$TS.tar.gz"
else
echo "[FAIL] 归档损坏,删除"; rm -f "$DEST/www-$TS.tar.gz"; exit 1
fi
# 清理旧归档
ls -1t "$DEST"/www-*.tar.gz | tail -n +$((KEEP + 1)) | xargs -r rm -f
tar 的压缩选项要选对:
| 选项 | 算法 | 速度 | 压缩率 | 建议 |
|---|---|---|---|---|
-z |
gzip | 中 | 中 | 通用,兼容性最好 |
-j |
bzip2 | 慢 | 较高 | 归档不常解压时用 |
-J |
xz | 很慢 | 最高 | 长期归档、空间紧张 |
--zstd |
zstd | 快 | 高 | 需较新 tar,兼顾速度与压缩率 |
| 不加 | 仅打包 | 最快 | 无 | 已压缩的文件(图片/视频)不必再压 |
一个经验判断:文本类数据(代码、日志、配置)压缩率可达 5:1 到 10:1;已经压缩过的文件(jpg、zip、视频)再压几乎没收益,反而白耗 CPU。所以对图片、视频目录,打包不压缩(不加 -z)可能更合理。
五、恢复演练与验证
备份不验证等于没有备份。至少要做两件事:每次备份后校验归档能否正常列出/解压,以及定期从备份恢复一份到临时目录做比对。
# 1) 校验归档完整性(tar)
tar -tzf /backup/archives/www-2027-01-01.tar.gz >/dev/null && echo "归档OK"
# 2) 用 rsync 校验:dry-run 对比备份与源,看差异
rsync -avn --delete /data/www/ /backup/www/2027-01-01/ | tail -20
# -n 是 dry-run,只列出会做的动作,不实际执行
# 3) 真正恢复演练:解到临时目录
mkdir -p /tmp/restore_test
tar -xzf /backup/archives/www-2027-01-01.tar.gz -C /tmp/restore_test/
diff -r /tmp/restore_test/www /data/www | head # 对比差异
# 4) 从 rsync 版本恢复:直接 rsync 回去即可
rsync -a --delete /backup/www/2027-01-01/ /data/www/
第 4 条命令是恢复的核心,但要注意它带 --delete——它会把 /data/www 里当前存在、而备份里没有的文件删掉,让目录完全回到备份时的状态。如果只想"把备份里多出来的文件补回去、不删现在的文件",就去掉 --delete。恢复比备份更需要谨慎,因为操作对象是生产数据。恢复前务必先对现状再做一次备份,给自己留后路。
六、定时任务与异地同步
# 每日 02:00 做增量备份,02:30 同步到异地(建议错开时间,避免同抢 IO)
sudo tee /etc/cron.d/backup_dir >/dev/null <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 2 * * * root /usr/local/bin/backup_dir.sh >> /var/log/backup_cron.out 2>&1
30 3 * * * root rsync -az --bwlimit=50000 /backup/www/ backupuser@remote:/backup/www/ >> /var/log/backup_cron.out 2>&1
EOF
sudo chmod 644 /etc/cron.d/backup_dir
异地同步用 --bwlimit 限速(单位 KB/s)很有必要,否则备份流量可能把正常业务带宽挤占掉。50000 约等于 50MB/s,是经验值,请按你实际带宽调整。跨机同步建议用 SSH 密钥认证并限制该密钥只能执行 rsync(在目标端 authorized_keys 里加 command= 限制),避免密钥泄露后被当成登录凭证。
七、风险提示与回滚
第一,--delete 是双刃剑。 它保证备份与源一致,但源端误删会传导到备份端。规避办法有三:一是在备份脚本里对源目录做存在性检查(空目录或不存在就别跑,直接告警);二是备份端保留多天版本而不是单一镜像;三是关键数据做两份独立策略。
# 在脚本开头加防护:源目录不存在或为空则中止
if [ ! -d "$SRC" ] || [ -z "$(ls -A "$SRC" 2>/dev/null)" ]; then
echo "[ABORT] 源目录 $SRC 不存在或为空,疑似异常,终止备份"; exit 1
fi
第二,别把备份放在同一块盘。 如果备份目录和源数据在同一块物理盘上,盘坏了两个一起没,等于没备份。至少放到独立磁盘或独立挂载点,条件允许再同步一份到异地。"3-2-1 原则"(3 份副本、2 种介质、1 份异地)是行业共识。
第三,保留策略要算空间账。 rsync 硬链接方案下,保留 N 天大约占 "1 份全量 + N×每日变化量" 的空间,比 N 份全量省太多;而 tar 方案是实打实的 N 份全量。设计保留天数时要用 du -sh 实测增长速度,别拍脑袋。
回滚方式:本地版本可直接从 /backup/www/<日期>/ 取用(rsync 恢复或直接拷文件);tar 归档用 tar -xzf 解压。删除的过期版本无法找回,所以对关键数据一定要额外做异地保留。若误删了备份目录,只能从异地副本恢复——这正是异地备份存在的意义。
常见问题
Q1:rsync 的 --delete 到底要不要加?
看需求。做"镜像同步"(备份端要跟源端完全一致)时加;做"版本归档"时可不加,甚至建议不加,因为多删的文件留着反而是保护。关键是要理解:加了 --delete,源端删除会传导到备份端,所以必须配合多版本保留或独立防护。
Q2:为什么保留 7 天只占了一点点空间?
因为你用了 --link-dest。未变化的文件在不同日期目录之间是硬链接,共享同一份数据,只有变化的文件真正占新空间。用 du -sh 看单个日期目录会显得很大,但看整个备份根目录才是真实占用(du 对硬链接只算一次)。
Q3:增量备份能恢复到任意一天吗?
用 --link-dest 方案可以,因为每个日期目录在逻辑上都是完整快照(未变文件通过硬链接补齐)。而用 --delete 的单一镜像方案不能,它只保留当前状态。tar 方案能否恢复任意一天取决于你有没有保留那天的归档。
Q4:备份很慢,怎么优化?
几方面:排除无用目录(缓存、node_modules、临时文件);用 --bwlimit 避免影响业务但也别设太低;大数据量首次全量慢是必然的,之后增量就快了;如果数据量特别大,考虑文件系统快照(LVM/云盘)做快速起点,再慢慢同步。
Q5:怎么知道备份是不是真的成功?
三看:一看备份脚本的退出码和日志;二看备份目录的日期、文件数量、总大小是否合理;三做定期恢复演练。只写日志不验证是"假成功"的温床,很多团队都是等真要用时才发现备份文件是空的或损坏的。
总结
- 备份方案要回答"备什么、留多久、怎么恢复"三个问题,操作系统和缓存不必备,关键数据才要。
- 用
rsync --link-dest做增量历史版本:每个日期都是完整快照,未变文件共享硬链接,空间占用远低于多份全量。 tar -czf适合打包归档,注意文本可高效压缩、媒体文件没必要再压;每次备份后校验归档完整性。- 先写临时目录再原子改名,避免残缺备份被当成完整版本;脚本开头要检查源目录存在且非空。
--delete会让源端删除传导到备份端,必须配合多版本保留或独立防护;备份务必放到独立磁盘并同步一份异地,且定期做恢复演练。
企业QQ咨询




