Linux-12 归档、压缩与 rsync:tar 的古怪参数、压缩算法选型与危险的 --delete
这一篇讲「把一堆文件变成一个文件」和「把文件搬到别处」。看起来是最简单的操作,但 tar 的参数体系和 rsync 的斜杠语义,是运维事故的两个经典来源。
先看五个问题:
tar的参数为什么可以不带横杠(tar czvf而不是tar -c -z -v -f)?这种风格从哪来?gzip、bzip2、xz、zstd该选哪个?tar打包时警告Removing leading '/' from member names是什么意思?不去掉会怎样?rsync -a /src /dst和rsync -a /src/ /dst有什么区别?rsync --delete什么情况下会把目标目录清空?
1. 归档与压缩是两件事
这是理解整个工具链的前提:Unix 把「打包」和「压缩」拆成了两个独立的工具。
多个文件 一个归档文件 压缩后的文件
+-------+
| a.txt | \ +------------+ +---------------+
+-------+ \ tar | | gzip | |
+-------+ +-----------> | archive.tar | -----------> | archive.tar.gz|
| b.txt | / (归档) | | (压缩) | |
+-------+ / +------------+ +---------------+
+-------+ /
| c/ | /
+-------+
tar = 只负责把多个文件拼成一个流(保留路径、权限、属主、时间戳)
gzip = 只负责把一个字节流压小(不认识文件的概念)
为什么这样拆? 这是 Unix 管道哲学的直接体现——每个工具只做一件事,通过管道组合:
tar -cf - dir | gzip > dir.tar.gz # 手动组合(tar 的 -z 就是自动做这个)
tar -cf - dir | zstd > dir.tar.zst # 换个压缩算法,tar 完全不用改
tar -cf - dir | ssh host 'tar -xf -' # 甚至可以不落盘直接传到另一台机器
tar -cf - dir | gpg -c > dir.tar.gpg # 或者接加密
对比 zip 的做法(Windows 世界的主流):
.tar.gz(先归档后压缩) |
.zip(每个文件单独压缩) |
|
|---|---|---|
| 压缩比 | 更好(跨文件共享压缩字典) | 较差(每个文件独立压缩) |
| 随机访问 | ❌ 必须解压整个流才能取出末尾的文件 | ✅ 可以直接跳到某个文件 |
| 保留 Unix 元数据 | ✅ 权限、属主、软链接、稀疏文件 | ⚠️ 支持有限(跨平台妥协) |
| 损坏影响 | 损坏点之后的内容全部丢失 | 只影响单个文件 |
| 跨平台 | Linux/macOS 原生 | Windows 原生 |
所以选择很清楚:Linux 内部流转用 .tar.gz/.tar.zst,要发给 Windows 用户用 .zip。
2. tar
2.1 名字与古怪参数的来源
tar = tape archive(磁带归档)。它诞生于 1979 年,设计目标是把文件写到磁带上。这解释了它的很多特性:
- 参数不带横杠(
tar cvf)—— 那个年代 getopt 还没成为标准,早期 Unix 命令的参数风格各异。tar保留了这种「第一个参数是操作码」的老式风格 - 顺序流式处理 —— 磁带只能顺序读写,不能随机访问,所以 tar 格式是纯流式的
- 默认写到
/dev/rmt0—— 早期版本默认输出到磁带设备,所以必须用-f显式指定文件 -r(append)不能用于压缩包 —— 追加需要定位到流末尾,压缩流做不到
# 三种参数风格都能用(这是历史包袱的直接结果)
tar cvzf backup.tar.gz dir # 老式:操作码不带横杠
tar -cvzf backup.tar.gz dir # 短选项风格
tar --create --verbose --gzip --file=backup.tar.gz dir # 长选项风格
# ⚠️ 老式风格里,f 后面必须紧跟文件名
tar czf backup.tar.gz dir # ✅ f 在最后,紧跟文件名
tar cfz backup.tar.gz dir # ❌ f 后面跟的是 z,把 "z" 当成了文件名
# 报错:tar: z: Cannot open: No such file or directory
2.2 操作模式:必须选一个
| 模式 | 长选项 | 作用 |
|---|---|---|
-c |
--create |
创建归档 |
-x |
--extract |
解开归档 |
-t |
--list |
列出内容(解压前必做) |
-r |
--append |
追加文件(不能用于压缩包) |
-u |
--update |
只追加比归档里更新的文件 |
-d |
--diff / --compare |
比较归档与文件系统的差异 |
--delete |
— | 从归档中删除(不能用于压缩包) |
-A |
--concatenate |
合并多个 tar 归档 |
2.3 常用修饰参数
| 短 | 长 | 作用 |
|---|---|---|
-f FILE |
--file |
指定归档文件名(几乎必须;- 表示 stdin/stdout) |
-v |
--verbose |
显示处理的文件(-vv 更详细) |
-z |
--gzip |
用 gzip 压缩/解压(.tar.gz / .tgz) |
-j |
--bzip2 |
用 bzip2(.tar.bz2) |
-J |
--xz |
用 xz(.tar.xz) |
| — | --zstd |
用 zstd(.tar.zst) |
| — | --lzma / --lzop |
其他算法 |
-a |
--auto-compress |
根据文件名后缀自动选压缩算法(创建时很方便) |
-C DIR |
--directory |
切换到该目录再操作(打包解包都常用) |
-p |
--preserve-permissions |
保留权限(解包时 root 默认开启,普通用户要显式加) |
| — | --same-owner |
保留属主(root 解包时默认) |
| — | --no-same-owner |
不保留属主(普通用户默认) |
| — | --numeric-owner |
用 uid/gid 数字而不是名字(跨机器恢复必用) |
--exclude=PAT |
— | 排除匹配的路径 |
--exclude-from=FILE |
— | 从文件读排除列表 |
-X FILE |
同上 | 简写 |
--strip-components=N |
— | 解压时去掉前 N 层目录 |
-P |
--absolute-names |
保留绝对路径(危险,见 2.5) |
-h |
--dereference |
跟随软链接(打包目标内容而不是链接本身) |
-S |
--sparse |
高效处理稀疏文件 |
-O |
--to-stdout |
解压到标准输出 |
--to-command=CMD |
— | 把每个文件交给命令处理 |
-g FILE |
--listed-incremental |
增量备份(记录快照状态) |
--overwrite |
— | 覆盖已有文件(默认行为取决于版本) |
-k |
--keep-old-files |
不覆盖已有文件 |
--skip-old-files |
— | 跳过已存在的(不报错) |
-m |
--touch |
不恢复修改时间 |
--checkpoint=N |
— | 每 N 条记录打个点(配合 --checkpoint-action 显示进度) |
--totals |
— | 结束时显示总字节数与速度 |
-W |
--verify |
写完后校验 |
--warning=no-XXX |
— | 关闭特定警告 |
2.4 常用配方
# ── 打包 ──
tar -czvf backup.tar.gz /path/to/dir # gzip 压缩(最通用)
tar -cJvf backup.tar.xz /path/to/dir # xz(压缩比最高,慢)
tar --zstd -cvf backup.tar.zst dir # zstd(推荐:快且压缩比接近 xz)
tar -cavf backup.tar.gz dir # -a 根据后缀自动选算法
# ✅ 用 -C 避免打包出多余的目录层级(很重要的习惯)
tar -czf app.tar.gz -C /opt/app . # 归档内是 ./bin ./conf(干净)
tar -czf app.tar.gz /opt/app # 归档内是 opt/app/bin(多两层)
tar -czf app.tar.gz -C /opt app # 归档内是 app/bin(一层,也常用)
# ── 查看(解压前必做!)──
tar -tzvf backup.tar.gz # 列出内容与权限
tar -tzvf backup.tar.gz | head -20 # 只看前几个,确认目录结构
tar -tzvf backup.tar.gz | wc -l # 数文件个数
# ⚠️ 不先看就解压的风险:归档里可能是散落的文件(没有顶层目录),
# 会把当前目录搞得一团糟(这叫 "tarbomb")
# ── 解压 ──
tar -xzvf backup.tar.gz # 解到当前目录
tar -xzvf backup.tar.gz -C /target # ✅ 解到指定目录(目录必须已存在)
tar -xzvf backup.tar.gz --strip-components=1 # 去掉最外面一层目录
# ^^^^^^^^^^^^^^^^^^^ GitHub 下载的 tarball 常用
tar -xzvf backup.tar.gz path/inside/file # 只解压某一个文件
tar -xzvf backup.tar.gz --wildcards '*.conf' # 只解压匹配的
tar -xzOf backup.tar.gz path/to/file # 解压某文件到 stdout(不落盘查看)
tar -xzvf backup.tar.gz -k # 不覆盖已存在的文件
# 现代 tar 能自动识别压缩格式,解压时可以省掉 z/j/J
tar -xvf backup.tar.gz # ✅ 自动识别
tar -xvf backup.tar.xz
tar -xvf backup.tar.zst
# ── 排除 ──
tar -czf app.tar.gz --exclude='*.log' --exclude='node_modules' --exclude='.git' -C /opt/app .
tar -czf app.tar.gz -X exclude.txt -C /opt/app .
cat exclude.txt
# *.log
# node_modules
# .git
# ⚠️ --exclude 必须写在【要打包的路径之前】,否则可能不生效(GNU tar 的老版本)
# ── 追加与合并(只能对未压缩的 .tar)──
tar -rvf archive.tar newfile.txt # 追加文件
tar -Avf all.tar part1.tar part2.tar # 合并归档
# 压缩包要追加只能:解压 -> 追加 -> 重新压缩
# ── 权限与属主 ──
sudo tar -xzvpf backup.tar.gz -C / # -p 保留权限(恢复系统备份时必须)
sudo tar -xzvf backup.tar.gz --numeric-owner -C /
# ^^^^^^^^^^^^^^^ 用 uid/gid 数字,避免跨机器时用户名映射错乱
tar -czf backup.tar.gz --numeric-owner dir # 打包时也建议加
# ── 进度显示 ──
tar -czf backup.tar.gz --checkpoint=1000 --checkpoint-action=dot dir
tar -czf backup.tar.gz --totals dir # 结束时报总量与速度
tar -cf - dir | pv -s $(du -sb dir | cut -f1) | gzip > backup.tar.gz
# ^^ pv 提供进度条(需要安装)
# ── 跨机器传输(不落中间文件)──
tar -czf - /data | ssh user@host 'cat > /backup/data.tar.gz'
tar -cf - /data | ssh user@host 'tar -xf - -C /restore' # 直接解到对端
ssh user@host 'tar -czf - /data' | tar -xzf - -C /local # 从对端拉取
# 这个用法对内网大目录传输很实用(省掉打包落盘的时间和磁盘)
2.5 开篇第三问:绝对路径陷阱
tar -czf backup.tar.gz /etc/nginx
# tar: Removing leading `/' from member names
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 这个警告是【好事】
tar 默认把归档里的路径改成相对路径(etc/nginx/... 而不是 /etc/nginx/...)。原因是安全:
# 如果保留绝对路径(-P),解压时会【无视你的当前目录】直接写到那个绝对位置
tar -czPf backup.tar.gz /etc/nginx # -P 保留绝对路径
cd /tmp && tar -xzPf backup.tar.gz
# 结果:直接覆盖了 /etc/nginx,而不是解到 /tmp/etc/nginx 💀
# 更危险的是恶意归档
# 攻击者构造一个含 /etc/cron.d/backdoor 或 ../../../../etc/passwd 的归档,
# 你一解压就被覆盖了系统文件
# ✅ 所以默认行为(去掉开头的 /)是正确的,解压时会落在当前目录下
cd /tmp && tar -xzf backup.tar.gz
ls /tmp/etc/nginx # 解到了这里,安全
推论:恢复系统备份到原位时,正确做法不是用 -P,而是:
sudo tar -xzvpf backup.tar.gz -C / # ✅ 用 -C / 指定解压根位置
# ^^^^ 明确、可控
另外要防 tarbomb 和路径穿越:
# 解压前必看内容
tar -tzf unknown.tar.gz | head -20
tar -tzf unknown.tar.gz | grep -E '^/|\.\./' # 检查有没有绝对路径或 ..
# 现代 GNU tar 默认会拒绝 ../ 穿越,但老版本和其他实现不一定
# 安全的解压姿势:解到一个空的临时目录
mkdir -p /tmp/extract && tar -xzf unknown.tar.gz -C /tmp/extract
ls /tmp/extract # 确认结构后再挪到目标位置
2.6 其他陷阱
# ① 软链接:默认打包链接本身,不是目标内容
tar -czf backup.tar.gz dir # 归档里是软链接
tar -czhf backup.tar.gz dir # -h 跟随链接,打包实际内容(体积变大)
# 部署场景常需要 -h(把 current -> releases/v1.2.3 变成实体)
# ② 硬链接:tar 能正确识别并只存一份
tar -czf backup.tar.gz dir # 硬链接在归档里被标记,解压后仍是硬链接 ✅
# ③ 稀疏文件:不加 -S 会把空洞展开成真实的 0(体积暴涨)
tar -czSf vm.tar.gz disk.img # -S 高效处理稀疏文件
# ④ 时间戳:默认保留 mtime;想让 tar 包内容可复现(reproducible build)
tar --sort=name --mtime='2026-01-01 00:00:00' --owner=0 --group=0 --numeric-owner \
-czf reproducible.tar.gz dir
# ^^^^^^^^^^^ 固定顺序 + 固定时间戳 + 固定属主 = 内容相同则 tar 包字节相同
# ⑤ 文件名编码:GBK 文件名的归档在 UTF-8 系统上会乱码
tar -czf backup.tar.gz dir # tar 不做编码转换,原样存字节
convmv -f gbk -t utf8 -r --notest dir/ # 打包前先转换文件名编码
# ⑥ 大文件与 ARG_MAX:文件太多时命令行会超长
tar -czf backup.tar.gz $(find . -name '*.log') # ❌ 可能 Argument list too long
find . -name '*.log' -print0 | tar -czf backup.tar.gz --null -T - # ✅
# ^^^^^^ 从 stdin 读文件列表
# ⑦ 校验归档完整性
gzip -t backup.tar.gz # 测试 gzip 流是否完好
tar -tzf backup.tar.gz > /dev/null && echo OK # 能完整列出说明没坏
sha256sum backup.tar.gz > backup.tar.gz.sha256 # ✅ 备份时同时存校验和
sha256sum -c backup.tar.gz.sha256 # 恢复前先校验
2.7 增量备份
# 用 -g(--listed-incremental)记录状态快照
# 全量(level 0)
sudo tar -czf full.tar.gz -g /var/backups/snapshot.snar -C /data .
# 增量(level 1):只打包自上次以来变化的文件
sudo tar -czf inc1.tar.gz -g /var/backups/snapshot.snar -C /data .
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 同一个 snar 文件会被更新
# 恢复:必须【按顺序】解压全量 + 所有增量
sudo tar -xzf full.tar.gz -g /dev/null -C /restore
sudo tar -xzf inc1.tar.gz -g /dev/null -C /restore
# ^^^^^^^^^^^ 恢复时用 /dev/null(增量恢复要求)
# ⚠️ 三个问题让它在实践中不太好用:
# ① snar 文件丢了就无法继续增量
# ② 恢复必须按顺序,任何一个增量包损坏就断链
# ③ 删除的文件需要正确处理(tar 会记录,但容易出错)
# ✅ 生产上推荐用专门的备份工具(restic/borg/rsync --link-dest,见第 6 章)
3. 压缩工具选型
3.1 开篇第二问:实测对比
以一个 1GB 的混合文本+二进制目录为参考(具体数值因数据而异,看的是数量级关系):
| 工具 | 后缀 | 压缩比 | 压缩速度 | 解压速度 | 内存 | 多线程 |
|---|---|---|---|---|---|---|
gzip |
.gz |
基准(约 3:1) | 快 | 很快 | 低 | pigz |
bzip2 |
.bz2 |
比 gzip 好 ~10% | 很慢 | 慢 | 中 | pbzip2 |
xz |
.xz |
最好(比 gzip 好 ~30%) | 极慢 | 中 | 高(可达 GB 级) | -T0 |
zstd |
.zst |
接近 xz | 快(接近 gzip) | 极快 | 中 | -T0 |
lz4 |
.lz4 |
最差 | 极快 | 极快 | 极低 | -T0 |
brotli |
.br |
好(Web 场景优化) | 中 | 快 | 中 | — |
关键结论:
zstd 在「压缩比 × 速度」上全面优于 gzip 和 bzip2,是新项目的默认选择。
xz 只在「压缩一次、分发很多次」(如发行版镜像、软件包)时值得用。
lz4 用于「实时压缩」(如内存压缩、日志实时传输)。
gzip 的唯一优势是【无处不在的兼容性】。
3.2 决策树
需要什么?
├─ 兼容性最重要(发给别人、老系统) -> gzip (.tar.gz)
├─ 日常备份、内部流转 -> zstd (.tar.zst) ← 推荐
├─ 压缩一次分发很多次(镜像、软件包) -> xz (.tar.xz)
├─ 实时/流式压缩(日志管道、内存) -> lz4 或 zstd -1
├─ 要发给 Windows 用户 -> zip
└─ 需要随机访问单个文件 -> zip 或 squashfs
3.3 各工具的参数
# ── gzip ──
gzip file # 压缩(【删除原文件】,变成 file.gz)
gzip -k file # k = keep,保留原文件
gzip -c file > file.gz # 或者用 -c 输出到 stdout
gzip -d file.gz # 解压(等价 gunzip)
gzip -1 file # 最快(压缩比最低)
gzip -9 file # 最慢(压缩比最高),默认是 -6
gzip -t file.gz # 测试完整性
gzip -l file.gz # 查看压缩率
gzip -r dir # 递归压缩目录里【每个文件】(不是打包!)
pigz -p 8 file # 多线程 gzip(8 线程)
# ── zstd(推荐)──
zstd file # 压缩(默认保留原文件,与 gzip 相反!)
zstd --rm file # 压缩后删除原文件
zstd -d file.zst # 解压
zstd -19 file # 最高压缩比(1~19,默认 3)
zstd --ultra -22 file # 超高(需要更多内存)
zstd -T0 file # 用所有 CPU 核心
zstd -1 file # 最快
zstd --long=27 file # 长距离匹配(大文件效果好)
zstd --adapt file # 自适应压缩级别(管道场景,根据 IO 速度调整)
zstd -c file | ... # 输出到 stdout
# ── xz ──
xz file # 压缩(删除原文件)
xz -k file # 保留原文件
xz -d file.xz # 解压(等价 unxz)
xz -9e file # 极限压缩(e = extreme)
xz -T0 file # 多线程
xz -T4 -9 file # 4 线程 + 高压缩
xz --memlimit=2G -d file.xz # 限制解压内存(解压高压缩比文件可能需要很多内存)
# ── bzip2 ──
bzip2 file / bunzip2 file.bz2
pbzip2 -p8 file # 多线程版
# ── lz4 ──
lz4 file # 极快
lz4 -9 file # 高压缩(仍然很快)
lz4 -d file.lz4
# ── 查看压缩文件(不解压)──
zcat / zless / zgrep / zdiff # gzip
bzcat / bzless / bzgrep # bzip2
xzcat / xzless / xzgrep # xz
zstdcat / zstdgrep # zstd
3.4 多线程压缩:常被忽略的加速
# 单线程 xz 压缩 10GB 可能要一小时,多线程能快 8 倍
tar -cf - /data | xz -T0 > data.tar.xz # xz 内置多线程
tar -cf - /data | zstd -T0 -19 > data.tar.zst # zstd 内置多线程
tar -cf - /data | pigz -p8 > data.tar.gz # gzip 靠 pigz
# tar 直接指定压缩程序
tar -cf data.tar.gz -I 'pigz -p8' /data
tar -cf data.tar.zst -I 'zstd -T0 -19' /data
tar -cf data.tar.xz -I 'xz -T0 -9' /data
# ^^ -I / --use-compress-program 指定外部压缩命令
# 也可以直接设置环境变量
export GZIP='-9' # 老式方式(已废弃,会有警告)
export ZSTD_CLEVEL=19
export ZSTD_NBTHREADS=0
4. zip:跨平台场景
zip -r archive.zip dir # -r 递归(不加只会打包目录本身)
zip -r archive.zip dir -x '*.log' '*/node_modules/*' # -x 排除
zip -9 -r archive.zip dir # 最高压缩
zip -e archive.zip file # 加密(提示输入密码)
zip -s 100m -r big.zip dir # 分卷(每卷 100MB)
zip -j archive.zip dir/* # j = junk paths,不保留目录结构
zip -u archive.zip newfile # 更新已有归档
unzip archive.zip # 解压
unzip -l archive.zip # 列出内容(解压前必看)
unzip -d /target archive.zip # 解到指定目录
unzip -o archive.zip # 覆盖已有文件(不提示)
unzip -n archive.zip # 不覆盖
unzip archive.zip 'path/*' # 只解压匹配的
unzip -p archive.zip file > out # 解压到 stdout
unzip -t archive.zip # 测试完整性
两个常见问题:
# ① 中文文件名乱码(Windows 用 GBK,Linux 用 UTF-8)
unzip archive.zip
# 文件名变成 ��ĵ�.txt
unzip -O GBK archive.zip # ✅ Debian/Ubuntu 的 unzip 支持 -O
unzip -O CP936 archive.zip # 同上(CP936 = GBK)
# 不支持 -O 时用 7z 或 Python
7z x archive.zip # 7z 通常能正确处理
python3 -c "
import zipfile, sys, os
z = zipfile.ZipFile(sys.argv[1])
for n in z.namelist():
fixed = n.encode('cp437').decode('gbk')
print(fixed)
z.extract(n)
if n != fixed: os.rename(n, fixed)
" archive.zip
# ② zip 的加密不安全
zip -e archive.zip file # 传统 ZipCrypto,早已被破解
# ✅ 需要加密用 GPG 或 age
tar -czf - dir | gpg -c > backup.tar.gz.gpg # 对称加密
tar -czf - dir | age -r <公钥> > backup.tar.gz.age # 现代方案
7z a -p -mhe=on archive.7z dir # 7z 的 AES-256(-mhe 连文件名一起加密)
5. rsync
5.1 它凭什么比 cp 好
rsync = remote sync。三个核心能力:
① 增量传输(两级判断)
第一级:quick check(默认)
比较「文件大小 + mtime」,两者都一样就认为文件没变 -> 直接跳过
代价:mtime 被改过的相同文件会被重传;内容变了但 size+mtime 恰好一样会被漏掉(极罕见)
第二级:rolling checksum(跨机器传输时对【已变化的文件】)
把文件切成固定大小的块,对每块算弱校验(rolling)+ 强校验(MD5)
接收端把自己的块校验发给发送端,发送端只传【对不上的块】
-> 一个 10GB 文件改了 1MB,只传那 1MB 附近的内容
② 能中断续传、能限速、能显示进度 —— cp 三个都不行
③ 能镜像删除(--delete)—— 让目标和源完全一致
# 一个直观的对比
cp -a /data/ /backup/ # 每次都全量复制 10GB
rsync -a /data/ /backup/ # 第一次全量,之后只传变化的部分(可能只有几 MB)
5.2 开篇第四问:末尾斜杠的语义
这是 rsync 最著名的坑,务必记牢:
rsync -a /src /dst/ # -> /dst/src/... 把 src 这个【目录本身】放进 dst
rsync -a /src/ /dst/ # -> /dst/... 把 src 的【内容】放进 dst
# ^ 就差这一个斜杠
记忆方法:源路径末尾的 / 表示「里面的东西」。
# 实际演示
mkdir -p /tmp/a/sub && touch /tmp/a/f1 /tmp/a/sub/f2 && mkdir -p /tmp/b
rsync -a /tmp/a /tmp/b/ && find /tmp/b
# /tmp/b
# /tmp/b/a <- 多了一层 a
# /tmp/b/a/f1
# /tmp/b/a/sub/f2
rm -rf /tmp/b/*
rsync -a /tmp/a/ /tmp/b/ && find /tmp/b
# /tmp/b
# /tmp/b/f1 <- 直接是内容
# /tmp/b/sub/f2
# 目标路径的斜杠【无关紧要】(rsync 总把它当目录)
rsync -a /tmp/a/ /tmp/b # 与 /tmp/b/ 等效
这个坑的危险之处在于它与 --delete 组合时:
# 你想同步内容,但漏了源的斜杠
rsync -a --delete /data /backup/
# 结果:数据被放进 /backup/data/,而 /backup/ 下【原有的其他内容全被删了】
# (因为 --delete 认为「源里只有 data 这一个条目」)
5.3 开篇第五问:–delete 什么时候会清空目标
--delete 的语义是「删除目标端存在但源端不存在的文件」,让两边完全一致。它在三种情况下会清空目标:
# ── 情况一:源路径写错或不存在(最常见)──
rsync -a --delete /dat/ /backup/ # 源目录名打错了(少了 a)
# rsync: change_dir "/dat" failed: No such file or directory
# ✅ 好消息:源【完全不存在】时 rsync 会报错退出,不会删
# ── 情况二:源目录存在但是空的(真正的杀手)──
# 场景:/data 是数据盘的挂载点,但盘没挂上,/data 是个空目录
mountpoint -q /data || echo "盘没挂上!"
rsync -a --delete /data/ /backup/
# 💀 源是空的 -> rsync 认为「所有文件都该删除」-> /backup 被清空
# ── 情况三:变量为空 ──
SRC="" # 变量赋值失败
rsync -a --delete "$SRC/" /backup/ # 展开成 rsync -a --delete / /backup/
# 把整个根目录同步过去…
# ── 情况四:斜杠错位(见 5.2)──
rsync -a --delete /data /backup/ # /backup 下除了 data/ 之外的都被删
四道安全带,生产脚本至少用前两个:
# ① --dry-run 演练(必做)
rsync -a --delete --dry-run /data/ /backup/ | head -50
rsync -avn --delete /data/ /backup/ # -n 是 --dry-run 的简写
# ^^^ 一定要先看它打算删什么
# ② --max-delete 限制删除数量(最实用的兜底)
rsync -a --delete --max-delete=100 /data/ /backup/
# 超过 100 个删除操作就中止并报错,避免灾难性清空
# ③ 校验源不为空 + 是挂载点
[[ -n "$(ls -A /data)" ]] || { echo "源目录为空,中止"; exit 1; }
mountpoint -q /data || { echo "数据盘未挂载,中止"; exit 1; }
# ④ 用 --backup 保留被删除/覆盖的文件
rsync -a --delete --backup --backup-dir=/backup/.trash/$(date +%F) /data/ /backup/current/
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 被删的文件挪到回收站而不是真删
5.4 -a 到底包含什么
rsync -a src/ dst/
# ^^ archive = -rlptgoD
# │││││││
# ││││││└─ D = --devices --specials 设备文件与特殊文件(需 root)
# │││││└── o = --owner 保留属主(需 root)
# ││││└─── g = --group 保留属组
# │││└──── t = --times 保留 mtime ← 【最关键】没有它每次都会重传!
# ││└───── p = --perms 保留权限
# │└────── l = --links 软链接照抄成软链接
# └─────── r = --recursive 递归
-a 不包含的三项,需要显式加:
rsync -aHAX src/ dst/
# ^^^
# │││
# ││└─ X = --xattrs 扩展属性(SELinux 标签、user.* 属性)
# │└── A = --acls ACL(10 篇第 6 章)
# └─── H = --hard-links 【保留硬链接关系】
# 不加 H 时,同一 inode 的两个名字会被复制成两份独立数据!
# 备份用了硬链接快照的目录时,不加 -H 会导致体积暴涨
# 完整的"一模一样"备份
rsync -aHAX --numeric-ids /data/ /backup/
# ^^^^^^^^^^^^^ 跨机器时用 uid/gid 数字,避免用户名映射错乱
5.5 完整参数表
| 短 | 长 | 作用 |
|---|---|---|
-a |
--archive |
= -rlptgoD(最常用的组合) |
-r |
--recursive |
递归 |
-l |
--links |
软链接照抄 |
-L |
--copy-links |
跟随软链接,复制目标内容 |
-p -t -g -o |
— | 保留权限/时间/属组/属主 |
-H |
--hard-links |
保留硬链接(-a 不含) |
-A |
--acls |
保留 ACL(-a 不含) |
-X |
--xattrs |
保留扩展属性(-a 不含) |
-v / -vv |
--verbose |
显示传输的文件 |
-n |
--dry-run |
演练(危险操作前必用) |
-z |
--compress |
传输时压缩(只在跨网络时有意义,本地是浪费 CPU) |
--compress-level=N |
— | 压缩级别 |
-P |
= --partial --progress |
断点续传 + 每文件进度 |
--info=progress2 |
— | 总体进度(比 -P 更适合大目录) |
--partial |
— | 保留部分传输的文件(下次续传) |
--partial-dir=DIR |
— | 断点数据放到指定目录(更安全) |
--append / --append-verify |
— | 只追加(日志类文件),后者会校验已有部分 |
--delete |
— | 删除目标端多余的文件 |
--delete-before/during/after/delay |
— | 删除的时机(默认 during) |
--delete-excluded |
— | 连被排除的也删 |
--max-delete=N |
— | 删除超过 N 个就中止(安全带) |
--max-size=SIZE |
— | 跳过大于此大小的文件 |
--min-size=SIZE |
— | 跳过小于此大小的 |
-c |
--checksum |
按内容校验而不是 size+mtime(慢但能发现静默损坏) |
-u |
--update |
跳过目标端更新的文件 |
--existing |
— | 只更新目标已有的文件,不新建 |
--ignore-existing |
— | 只新建,不更新已有的 |
--exclude=PAT |
— | 排除 |
--include=PAT |
— | 包含(顺序敏感,要写在 --exclude 之前) |
--exclude-from=FILE |
— | 从文件读排除规则 |
--files-from=FILE |
— | 只传列表里的文件 |
--from0 |
— | 列表用 \0 分隔(配合 find -print0) |
--bwlimit=RATE |
— | 限速(--bwlimit=50M) |
-e 'ssh -p 2222' |
--rsh |
指定远程 shell 与参数 |
--rsync-path=PATH |
— | 指定对端 rsync 的路径(如需 sudo rsync) |
--numeric-ids |
— | 用 uid/gid 数字(跨机器必用) |
--link-dest=DIR |
— | 与 DIR 中未变的文件建硬链接(增量快照核心) |
--copy-dest=DIR / --compare-dest=DIR |
— | 类似但用复制/仅比较 |
--backup / --backup-dir=DIR |
— | 覆盖/删除前先备份 |
--inplace |
— | 就地更新(省空间,但中断会留下损坏文件) |
--sparse |
— | 高效处理稀疏文件 |
--remove-source-files |
— | 传完删源(当「带进度的 mv」用) |
--stats |
— | 结束时输出详细统计 |
--itemize-changes / -i |
— | 显示每个文件为什么被传(调试神器) |
--log-file=FILE |
— | 写日志 |
--timeout=N |
— | IO 超时 |
--contimeout=N |
— | 连接超时 |
5.6 常用场景
# ── 本地备份 ──
rsync -aH --info=progress2 /data/ /backup/
rsync -aH --delete --max-delete=100 -n /data/ /backup/ # 先演练
rsync -aH --delete --max-delete=100 /data/ /backup/ # 确认后执行
# ── 跨机传输 ──
rsync -avz /app/ user@host:/app/ # 推(-z 跨网络值得开)
rsync -avz user@host:/app/ /app/ # 拉
rsync -avz -e 'ssh -p 2222 -i ~/.ssh/deploy' /app/ user@host:/app/
rsync -avz --rsync-path='sudo rsync' /app/ user@host:/opt/app/ # 对端需要 sudo
# ── 限速与断点(大文件/生产环境必备)──
rsync -aP --bwlimit=50M /big/ host:/big/ # 限速 50MB/s + 断点续传
rsync -aP --partial-dir=.rsync-partial /big/ host:/big/
# ── 只同步特定文件 ──
rsync -av --include='*/' --include='*.go' --exclude='*' src/ dst/
# ^^^^^^^^^^^^^ 必须先包含所有目录,否则 rsync 不会进入子目录
rsync -av --exclude-from=.rsyncignore src/ dst/
find . -name '*.log' -print0 | rsync -av --from0 --files-from=- . /backup/
# ── 排查「为什么这个文件被重传了」──
rsync -ain --itemize-changes /data/ /backup/ | head
# >f.st...... path/to/file
# ││││││││││
# ││└┴┴┴┴┴┴┴ 哪些属性不同:s=size t=time p=perms o=owner g=group a=acl x=xattr
# │└──────── 文件类型:f=文件 d=目录 L=软链接
# └───────── 操作:> 接收 < 发送 c 创建 h 硬链接 . 无变化
# ── 当「带进度的 mv」用 ──
rsync -aH --info=progress2 --remove-source-files /old/ /new/
find /old -type d -empty -delete # 源目录需要自己清理
# ── 校验已有备份的完整性 ──
rsync -avnc /data/ /backup/ # -c 按内容校验,-n 只报告
# ^^ 有输出说明内容不一致(能发现静默数据损坏)
5.7 增量快照:–link-dest
这是 Time Machine 式备份的核心技巧,也是 04 篇讲的硬链接的最佳应用:
#!/bin/bash
set -euo pipefail
SRC=/data
DST=/backup
TODAY=$(date +%F)
LATEST="$DST/latest"
# 安全检查
mountpoint -q "$SRC" || { echo "源未挂载"; exit 1; }
[[ -n "$(ls -A "$SRC")" ]] || { echo "源为空"; exit 1; }
rsync -aHAX --delete --max-delete=1000 \
--link-dest="$LATEST" \
"$SRC/" "$DST/$TODAY/"
# ^^^^^^^^^^^^^^^^^^^^^ 与上一次备份中【未变化】的文件建硬链接
ln -sfn "$DST/$TODAY" "$LATEST" # 更新 latest 指针(06 篇讲的原子切换)
效果:
du -sh /backup/2026-08-10 /backup/2026-08-11 /backup/2026-08-12
# 100G /backup/2026-08-10
# 100G /backup/2026-08-11 <- 每个都"看起来"是完整的 100G
# 100G /backup/2026-08-12
du -sh /backup/ # 但总占用远小于 300G
# 104G /backup/ <- 未变化的文件是硬链接,只占一份
# 每个快照都是【完整可用】的目录树,恢复时直接 cp 就行,不需要"重放增量"
cp -a /backup/2026-08-10/some/file /data/some/file
与 tar 增量备份对比:
tar -g 增量 |
rsync --link-dest |
|
|---|---|---|
| 恢复方式 | 必须按顺序解压全量+所有增量 | 每个快照直接可用 |
| 单点损坏 | 断链,后续全部无法恢复 | 只影响那一份 |
| 空间占用 | 更省(有压缩) | 稍多(未压缩,但未变文件共享) |
| 浏览历史 | 要解压才能看 | 直接 ls 就能看 |
| 依赖 | snar 文件不能丢 | 只依赖 latest 软链接(丢了就退化为全量) |
注意:--link-dest 要求源和目标在同一个文件系统(硬链接不能跨文件系统,04 篇 2.1)。
6. 备份策略
6.1 快照不是备份
LVM 快照、云盘快照、rsync --link-dest 到同一块盘 —— 这些都【不是备份】,
因为它们和原数据在同一组物理介质上。盘坏了、机器丢了、误删了整个卷,一起完。
备份的定义应该是:数据在【另一个故障域】里存在一份可恢复的副本。
6.2 3-2-1 原则
3 份副本(1 份生产 + 2 份备份)
2 种不同介质/位置(本地盘 + 对象存储 / 异地机房)
1 份离线或异地(防勒索软件、防区域性故障)
# 一个务实的落地方案
# ① 本地:rsync --link-dest 每日快照(快速恢复单个文件)
# ② 异地:restic/borg 加密备份到对象存储(防机房级故障)
# ③ 定期验证:每月做一次真实恢复演练
6.3 现代备份工具
tar + rsync 手搓脚本能解决 80% 的需求,但有三个短板:去重、加密、和「验证能否恢复」。这些用专门工具更合适:
# ── restic(推荐:Go 写的,单二进制,支持 S3/GCS/本地/SFTP)──
export RESTIC_REPOSITORY=s3:s3.amazonaws.com/mybucket/backup
export RESTIC_PASSWORD_FILE=/root/.restic-pass
restic init # 初始化仓库
restic backup /data --exclude-file=/etc/restic-excludes
restic snapshots # 列出所有快照
restic ls latest # 浏览快照内容
restic restore latest --target /restore # 恢复
restic restore latest --include /data/important.txt --target /tmp # 恢复单个文件
restic check --read-data # ✅ 验证仓库完整性(定期做)
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 保留策略 + 清理
# 特点:块级去重(改一行的 10GB 文件只增量存变化的块)、
# 默认加密、支持并发、快照不可变
# ── borg(类似,本地/SSH 场景性能更好)──
borg init --encryption=repokey /backup/repo
borg create /backup/repo::{now:%Y-%m-%d} /data
borg list /backup/repo
borg extract /backup/repo::2026-08-12
borg prune --keep-daily=7 --keep-weekly=4 /backup/repo
borg check /backup/repo
6.4 备份必须验证
# 「没验证过的备份等于没有备份」—— 这是运维界的老话
# 三个层次的验证:
# ① 完整性校验(能检测介质损坏)
sha256sum -c backup.tar.gz.sha256
restic check --read-data
tar -tzf backup.tar.gz > /dev/null && echo "归档结构完好"
# ② 抽样恢复(能检测备份内容错误)
mkdir /tmp/verify
tar -xzf backup.tar.gz -C /tmp/verify path/to/known/file
diff /tmp/verify/path/to/known/file /data/path/to/known/file
# ③ 完整恢复演练(唯一能真正证明备份可用的方式)
# 定期(如每季度)在一台干净机器上做完整恢复,并验证服务能正常启动
# 记录 RTO(恢复需要多久)和 RPO(会丢多少数据)
# 常见的「备份失效」原因
# - 备份脚本静默失败了几个月(没有监控告警)
# - 磁盘满了导致备份被截断
# - 加密密钥丢了(备份在,但打不开)
# - 备份了数据库文件但没做一致性快照(恢复出来是损坏的)
# - 保留策略配错,需要的那个时间点已经被清理了
# ✅ 备份脚本必须有成功/失败通知
#!/bin/bash
set -euo pipefail
trap 'curl -fsS "https://hc-ping.com/xxx/fail"' ERR
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 失败时通知(healthchecks.io 之类)
restic backup /data
restic check
curl -fsS "https://hc-ping.com/xxx" # 成功时打卡
# 这样「脚本静默失败了三个月」就不会发生 —— 没打卡就会告警
7. 知识点扩展
7.1 tar 速查
# 记忆口诀:c 创建 / x 解开 / t 查看,f 指定文件名,v 显示过程
tar -czvf out.tar.gz -C /src . # 打包(zstd 用 --zstd,xz 用 -J)
tar -tzvf out.tar.gz # 【先查看】
tar -xzvf out.tar.gz -C /dst # 解包
tar -xzvf out.tar.gz --strip-components=1 -C /dst # 去掉最外层目录
tar -xzOf out.tar.gz path/to/f # 只把某文件输出到 stdout
tar -czf - dir | ssh host 'tar -xzf - -C /dst' # 跨机传输
tar -cf out.tar.zst -I 'zstd -T0 -19' dir # 指定外部压缩程序(多线程)
7.2 压缩工具速查
| 需求 | 命令 |
|---|---|
| 通用兼容 | tar -czf x.tar.gz dir |
| 日常推荐 | tar --zstd -cf x.tar.zst dir |
| 极限压缩比 | tar -cJf x.tar.xz dir(或 xz -9e) |
| 多线程加速 | tar -cf x.tar.zst -I 'zstd -T0' dir / -I 'pigz -p8' / -I 'xz -T0' |
| 发给 Windows | zip -r x.zip dir |
| 加密 | tar -czf - dir | gpg -c > x.tar.gz.gpg / age / 7z -p -mhe=on |
| 查看不解压 | zcat zless zgrep(对应 bz/xz/zstd 前缀) |
| 校验完整性 | gzip -t x.gz / tar -tzf x.tar.gz >/dev/null / zstd -t |
7.3 rsync 速查
# ── 记住三件事 ──
# ① 源末尾的 / = "里面的内容"
# ② --delete 之前必须 --dry-run
# ③ -a 不含 -H -A -X
rsync -aH --info=progress2 /src/ /dst/ # 本地备份
rsync -avz -e 'ssh -p 2222' /src/ host:/dst/ # 跨机
rsync -avn --delete /src/ /dst/ # 【演练】删除操作
rsync -aH --delete --max-delete=100 /src/ /dst/ # 带安全带的镜像同步
rsync -aP --bwlimit=50M /big/ host:/big/ # 限速 + 断点
rsync -aHAX --link-dest=/backup/latest /src/ /backup/$(date +%F)/ # 增量快照
rsync -ain --itemize-changes /src/ /dst/ # 排查为什么重传
rsync -avnc /src/ /dst/ # 按内容校验备份是否一致
7.4 其他相关工具
# ── scp(简单文件传输,已被 rsync/sftp 取代)──
scp file host:/path/ # 单文件
scp -r dir host:/path/ # 递归
scp -P 2222 -i ~/.ssh/key file host:/path/
scp -C file host:/path/ # 压缩传输
scp -l 8000 file host:/path/ # 限速(单位 Kbit/s)
# ⚠️ OpenSSH 9.0+ 默认改用 SFTP 协议,某些老式通配符行为变了
# ✅ 大目录/需要断点续传时一律用 rsync
# ── sftp(交互式)──
sftp host
# get / put / ls / cd / lls / lcd / bye
sftp -b batch.txt host # 批处理模式
# ── 其他传输工具 ──
rclone sync /data remote:bucket/ # 同步到 S3/GCS/OneDrive 等 40+ 云存储
croc send file # 点对点传输(自动打洞,适合临时分享)
magic-wormhole # 类似
# ── split:分割大文件 ──
split -b 2G big.tar.gz part_ # 按大小分割成 part_aa part_ab...
split -n 4 big.tar.gz part_ # 分成 4 份
split -l 100000 huge.log part_ # 按行数分割
cat part_* > restored.tar.gz # 合并
# 用途:绕过单文件大小限制(FAT32 的 4GB、某些网盘)
# ── dd:块级复制 ──
dd if=/dev/sda of=disk.img bs=4M status=progress # 整盘镜像
dd if=disk.img of=/dev/sdb bs=4M status=progress # 写回
dd if=/dev/zero of=/swapfile bs=1M count=4096 # 造文件
# ⚠️ dd 的 if/of 写反会直接销毁数据,执行前反复确认
# ✅ 整盘克隆更推荐 ddrescue(能跳过坏块并记录进度)
sudo ddrescue /dev/sda /dev/sdb rescue.log
8. 面试题
Q:为什么 Linux 把「归档」和「压缩」分成 tar 和 gzip 两个工具?.tar.gz 和 .zip 有什么区别?
这是 Unix 管道哲学的直接体现:tar 只负责把多个文件拼成一个流(保留路径、权限、属主、软链接),gzip 只负责把一个字节流压小(它根本不认识"文件"这个概念)。拆开的好处是可以自由组合——换压缩算法(zstd/xz)、接加密(gpg)、直接管道到远程(ssh host 'tar -xf -'),tar 一行都不用改。
与 .zip 的核心区别:.tar.gz 是先归档再整体压缩,所以跨文件共享压缩字典、压缩比更好,但必须解压整个流才能取出末尾的文件;.zip 是每个文件单独压缩,所以能随机访问单个文件、损坏只影响一个文件,代价是压缩比差、对 Unix 元数据支持有限。
选择:Linux 内部流转用 .tar.gz/.tar.zst,发给 Windows 用户用 .zip。
Q:tar 打包时警告 Removing leading '/' from member names,为什么?不去掉会怎样?
tar 默认把归档内的路径转成相对路径(etc/nginx/ 而不是 /etc/nginx/),这是安全设计。
如果用 -P 保留绝对路径,解压时会无视当前目录直接写到那个绝对位置——你在 /tmp 里解压,结果覆盖了真正的 /etc/nginx。更危险的是恶意归档:攻击者构造一个包含 /etc/cron.d/backdoor 或 ../../../etc/passwd 的包,你一解压就被植入后门。
所以恢复系统备份到原位的正确做法不是 -P,而是 tar -xzvpf backup.tar.gz -C / —— 明确指定解压根位置。
配套的安全习惯:解压前先 tar -tzf 看内容(防 tarbomb:归档里是散落的文件,会把当前目录搞乱),用 grep -E '^/|\.\./' 检查有没有绝对路径或路径穿越,不确定的包解到空的临时目录。
Q:gzip、bzip2、xz、zstd 该怎么选?
关键结论是:zstd 在「压缩比 × 速度」上全面优于 gzip 和 bzip2,压缩比接近 xz 但速度接近 gzip,解压极快 —— 新项目应该默认用它。
gzip:唯一优势是无处不在的兼容性,需要发给别人或老系统时用bzip2:压缩比只比 gzip 好 10% 但慢很多,基本已被 zstd 取代xz:压缩比最好但极慢且吃内存,只在「压缩一次、分发很多次」(发行版镜像、软件包)时值得lz4:压缩比最差但极快,用于实时/流式场景(内存压缩、日志管道)
另一个常被忽略的点是多线程:zstd -T0、xz -T0、pigz -p8 能快数倍,用 tar -I 'zstd -T0 -19' 指定外部压缩程序。
Q:rsync -a /src /dst 和 rsync -a /src/ /dst 有什么区别?
源路径末尾的 / 表示「里面的内容」:
rsync -a /src /dst/→ 结果是/dst/src/...(把 src 这个目录本身放进 dst)rsync -a /src/ /dst/→ 结果是/dst/...(把 src 的内容放进 dst)
目标路径的斜杠无关紧要(rsync 总把它当目录)。
这个坑单独出现只是"多一层目录",但与 --delete 组合就是事故:rsync -a --delete /data /backup/ 会把数据放进 /backup/data/,同时删掉 /backup/ 下原有的所有其他内容(因为 rsync 认为源里只有 data 这一个条目)。
Q:rsync --delete 什么情况下会清空目标目录?怎么防?
四种情况:
- 源目录存在但是空的 —— 最危险。典型场景是数据盘没挂上,
/data是个空目录,rsync 认为「所有文件都该删」 - 变量为空 ——
rsync -a --delete "$SRC/" /backup/里$SRC未设置,展开成/ - 斜杠错位 —— 见上一题
- 源完全不存在时 rsync 会报错退出,反而是安全的
四道安全带:
rsync -avn --delete /data/ /backup/ # ① --dry-run 必做
rsync -a --delete --max-delete=100 ... # ② 删除超过 100 个就中止(最实用)
mountpoint -q /data && [[ -n "$(ls -A /data)" ]] || exit 1 # ③ 校验源
rsync -a --delete --backup --backup-dir=/backup/.trash/$(date +%F) ... # ④ 回收站
Q:rsync -a 包含哪些选项?它漏了什么?
-a = -rlptgoD:递归、软链接照抄、权限、时间、属组、属主、设备与特殊文件。其中 -t(保留 mtime)最关键——没有它,rsync 的 quick check 会认为所有文件都变了,每次都全量重传。
-a 不包含三项,需要显式加:
-H(硬链接) —— 不加时同一 inode 的两个名字会被复制成两份独立数据,备份硬链接快照目录时体积会暴涨-A(ACL)-X(扩展属性) —— SELinux 标签在这里
完整的"一模一样"备份是 rsync -aHAX --numeric-ids(最后这个在跨机器时避免用户名映射错乱)。
Q:rsync 为什么比 cp 快?它的增量判断是怎么做的?
两级判断:
- quick check(默认):只比较「文件大小 + mtime」,都一样就跳过。代价是 mtime 被改过的相同文件会被重传(要按内容判断得用
-c,慢但能发现静默损坏) - rolling checksum(跨机器传输已变化的文件时):把文件切块,用弱校验(滚动哈希)+ 强校验(MD5)比对,只传对不上的块。所以一个 10GB 文件改了 1MB,只传那 1MB 附近的内容
此外 cp 有三个做不到的:断点续传(--partial)、限速(--bwlimit)、进度(--info=progress2),以及镜像删除(--delete)和保留硬链接(-H)。
排查「为什么这个文件被重传了」用 rsync -ain --itemize-changes,输出的 >f.st...... 里每一位表示哪个属性不同(s=size、t=time、p=perms…)。
Q:怎么用 rsync 实现 Time Machine 式的增量快照?
用 --link-dest:与上一次备份中未变化的文件建硬链接,只有变化的文件才真正占新空间。
rsync -aHAX --delete --link-dest=/backup/latest /data/ /backup/$(date +%F)/
ln -sfn /backup/$(date +%F) /backup/latest
效果是:每个快照目录看起来都是完整的 100G,但总占用只比一份多一点(未变的文件共享同一个 inode)。而且每个快照都是完整可用的目录树,恢复时直接 cp 就行,不需要"重放增量"。
对比 tar -g 增量备份的优势:不需要按顺序解压全量+所有增量、单点损坏不断链、能直接 ls 浏览历史。限制是源和目标必须在同一个文件系统(硬链接不能跨文件系统)。
Q:LVM 快照、云盘快照算备份吗?
不算。 它们和原数据在同一组物理介质/同一个故障域里——盘坏了、机器丢了、整个卷被误删、区域性故障,一起完蛋。快照解决的是「误删单个文件」和「一致性备份的中间步骤」,不是「灾难恢复」。
备份的定义应该是:数据在另一个故障域里存在一份可恢复的副本。落地就是 3-2-1 原则:3 份副本、2 种介质/位置、1 份异地或离线(防勒索软件和区域性故障)。
更重要的一条是「没验证过的备份等于没有备份」。三个层次的验证:完整性校验(sha256sum -c、restic check --read-data)、抽样恢复、定期完整恢复演练(唯一能真正证明备份可用的方式,并顺带测出 RTO 和 RPO)。
常见的备份失效原因:脚本静默失败了几个月(所以必须有成功/失败通知打卡)、磁盘满导致备份被截断、加密密钥丢了、数据库没做一致性快照导致恢复出来是损坏的、保留策略把需要的时间点清掉了。
小结
- 归档与压缩是两件事:
tar拼流、gzip压流,拆开才能自由组合(换算法、接加密、直传远程) tar= tape archive,磁带出身解释了它的流式设计和古怪参数;-C控制目录层级、解压前先-t看内容- 绝对路径被去掉是安全设计,恢复到原位用
-C /而不是-P -a不含-H -A -X(tar 也一样要注意--numeric-owner)- 压缩选型:日常用 zstd(比 gzip 压得多且几乎一样快)、极限压缩比才用 xz、兼容性才用 gzip;别忘了多线程(
-T0/pigz) - rsync 三件事:源末尾
/= 内容、--delete前必--dry-run、-a漏了-H -A -X --delete清空目标的头号原因是「源目录存在但为空」(数据盘没挂上),用--max-delete+mountpoint -q兜底--link-dest做增量快照:每个快照都完整可用、总占用只多一点,比tar -g好用得多- 快照不是备份(同一故障域);没验证过的备份等于没有备份,脚本必须有失败告警
下一篇讲 进程与作业控制:ps aux 每一列到底是什么、进程状态 D 为什么杀不掉、kill -9 为什么有时无效、前台后台与 nohup/setsid 的区别、以及 lsof 和 /proc/<pid>/ 能告诉你的一切。
xingliuhua