rsync 一跑备份全没了:一次 --delete 与源目录未挂载的事故复盘

配了个每晚自动跑的备份脚本核心一行 rsync -av --delete 把数据盘 /mnt/databak 镜像到 /backup/db,跑了大半年稳稳当当,某天要从备份捞数据打开 /backup/db 发现几百个文件全没了空的,翻日志昨晚那次成功了没一个 error 却把备份全删了。排查梳理:rsync 不是 cp,cp 是把源叠加到目标 rsync 带 --delete 是镜像让目标变得和源一模一样源有目标没有就拷都有不同就更新源没有目标有就删,rsync 永远以源为唯一真相目标该长什么样完全由源此刻决定;源路径末尾斜杠 src/ 是拷目录里的内容 src 不带斜杠是拷目录本身目标多一层,带 --delete 时斜杠写错就是套娃叠加加误删;--delete 删的不是你删过的文件而是源里此刻没有目标里有的一切它没有记忆,源完整时这和删你删过的恰好等价所以平时很乖源一出问题就暴露会删光目标;事故根因是 /mnt/databak 是数据盘挂载点某次重启盘没自动挂载它就退回成根文件系统上一个空目录,rsync 没有源本该有几百个文件的记忆只认源此刻是空于是 --delete 把目标也清空 rsync 没 bug 错在把严格镜像命令建立在源一定可信这个没验证的前提上;给 --delete 上保险先 --dry-run 即 -n 空跑加 -i 看清每个文件动作,脚本里跑 rsync 前用 mountpoint -q 确认源已挂载 ls -A 确认源非空,配 --max-delete=N 当保险丝删太多就中止,set -u 防变量为空把路径变成根;最根本的 --delete 镜像不是备份它没有历史会把源的损坏同步过去真备份要留多个时间点。正确做法是跑前先确认源真的挂载非空,以及一套 rsync 备份排查纪律。

2023 年,一次"我跑了一下例行备份、第二天却发现备份目录空空如也"的事故,把我对"备份"这两个字的理解,从头到尾翻新了一遍。那台服务器上,我配了个每晚自动跑的备份脚本,核心就一行 rsync:把数据盘 /mnt/databak/ 里的东西,镜像到 /backup/db/。这脚本我跑了大半年,稳稳当当,我从没操过心。那天数据库出了点问题,我想从备份里捞一份前一天的数据出来。我打开 /backup/db/——空的。不是少了几个文件,是【一个文件都没有】。我整个人凉了半截:我的备份,是不是从来就没成功过?可我明明记得,前几天我还去看过,里头好好的几百个文件都在。我翻 rsync 的日志,发现昨晚那次备份,确实跑了,而且"成功"了——日志里没有一个 error。可它"成功"地,把 /backup/db/ 里的几百个文件,全删了。我盯着这个结果,脑子里嗡嗡的:一个【备份】脚本,它的职责不就是"保住数据"吗?它怎么会、凭什么会,反过来把我已经备好的数据,亲手删个精光?它昨晚到底做了什么?我下命令让它"备份",它却给了我一个"清空"——这中间,到底哪个环节,把我的意思理解反了?这件事逼着我把 rsync 的镜像本质、末尾斜杠、--delete 的真正危害、还有"镜像"和"备份"的天壤之别,彻底理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,数据盘挂在 /mnt/databak,每晚 cron 跑 rsync 备份
事故现象:
- 备份脚本核心:rsync -av --delete /mnt/databak/ /backup/db/
- 跑了大半年,一直正常
- ★ 某天打开 /backup/db/ —— 几百个备份文件全没了,空的
- rsync 日志显示昨晚那次"成功"了,没有一个 error

现场排查:
# 1. 看备份目录 —— 空的
$ ls /backup/db/
(什么都没有)                                   # ★ 备份全没了

# 2. ★ 看 rsync 昨晚的日志
$ grep deleting /var/log/backup.log
*deleting db_2023.sql
*deleting db_users.sql
*deleting ... (几百行)                          # ★★ 它在疯狂删文件!

# 3. ★ 关键:看源目录 /mnt/databak 现在什么样
$ ls /mnt/databak/
(空的)                                          # ★★ 源,也是空的!

# 4. ★ 这块数据盘,到底挂载了没有
$ mountpoint /mnt/databak
/mnt/databak is not a mountpoint                 # ★★ 没挂载!

$ df -h | grep databak
(无输出)                                        # ★ 确实没挂载

$ lsblk
vdb    50G disk                                  # ★ 盘在,但没挂上去

根因(后来想清楚的):
1. ★ /mnt/databak 是数据盘 vdb 的【挂载点】。盘挂着时,
   ls 它就是盘里的几百个文件。
2. ★ 某次重启后,这块盘【没有自动挂载上】(fstab 漏了
   /或盘临时故障)。挂载点 /mnt/databak 就退回成
   【根文件系统上一个普通的空目录】。
3. ★ cron 照常在半夜跑 rsync。rsync 去看源
   /mnt/databak/ —— 看到的是【空的】。
4. ★ rsync -av --delete 的意思是"让目标和源【一模
   一样】"。源是空的 -> 那目标也得是空的 -> --delete
   忠实地把 /backup/db/ 里几百个文件,全删了。
5. ★ rsync 没做错任何事。它完全正确地执行了"把目标
   同步成和源一致"。错的是:我们让它,对着一个
   【根本不是真源的空目录】,跑了这条命令。
6. 真相:rsync 只认"源此刻是什么样",它没有"源
   本该有几百个文件"这种记忆。源空了,它就把空,
   忠实地同步了过去。
不是 rsync 出 bug 了,是"源"在我不知道的时候
没了,而 rsync 把这个"没了",当成真相照办了。

修复 1:rsync 的本质是"镜像"——让目标变得和源一模一样

# === ★ 先把 rsync 到底在干什么,讲透 ===

# === ★ rsync 不是 cp,它做的是"镜像同步" ===
# 很多人(包括当初的我)把 rsync 当成"更聪明的 cp"。
#   这是事故的认知源头。
# ★ cp 的心智模型:把源【叠加】到目标上 —— 目标原来
#   有什么,cp 不管,只往里【加】。
# ★ rsync(尤其带 --delete)的心智模型:让目标,
#   变得和源【一模一样】。这是"镜像",不是"叠加"。

# === ★ "镜像"意味着三种动作 ===
# rsync 对比"源"和"目标",会做三件事:
#  ① ★ 源有、目标没有  -> 把它拷到目标(新增)
#  ② ★ 源目标都有但不同 -> 用源的版本覆盖目标(更新)
#  ③ ★ 源没有、目标有   -> 把目标里这个删掉(--delete)
$ rsync -av --delete /mnt/databak/ /backup/db/
# ★ ③ 这一条,就是本文事故的引信。

# === ★ 带不带 --delete,是两种完全不同的脾气 ===
# ★ 不带 --delete:只做 ①②,【只增不删】。目标里
#   多出来的东西,rsync 不碰,留着。
$ rsync -av /mnt/databak/ /backup/db/      # 温和:只增不删
# ★ 带 --delete:① ② ③ 全做,【严格镜像】。目标
#   必须和源分毫不差,源里没有的,一律删。
$ rsync -av --delete /mnt/databak/ /backup/db/   # 严格:会删

# === ★ 最关键的一句:rsync 永远以"源"为唯一真相 ===
# ★ 在 rsync 眼里,"目标该长什么样",这件事,
#   【完全、彻底】由源此刻的样子决定。
# ★ 目标自己原来有什么、积累了什么、对你多重要 ——
#   rsync 【一概不考虑】。源说了算。
# ★ 所以用 --delete,你其实是在对 rsync 说:"我向你
#   保证,这个源,就是目标【应该】有的全部。照它办。"
#   —— 一旦这个保证不成立,灾难就来了。

# === 认知 ===
# ★ rsync 不是 cp。cp 是把源叠加到目标;rsync 带
#   --delete 是"让目标变得和源一模一样"的镜像 ——
#   源有目标没有就拷、都有不同就更新、★ 源没有目标
#   有就删。rsync 永远以源为唯一真相,目标该是什么
#   样完全由源此刻的样子决定。

修复 2:末尾那个斜杠——src/ 和 src 是两回事

# === ★ rsync 最反直觉的坑:源路径末尾的 / ===

# === ★ 一个斜杠,决定"拷内容"还是"拷目录本身" ===
# 看这两条命令,只差源路径末尾一个 /:
$ rsync -av /data/src/ /dst/        # ★ 源【带】斜杠
$ rsync -av /data/src  /dst/        # ★ 源【不带】斜杠

# === ★ 源【带】斜杠 = 把这个目录"里面的内容" ===
$ rsync -av /data/src/ /dst/
# ★ 把 src 目录【里面的东西】,放进 dst。
#   结果:/dst/file1  /dst/file2 ...
# ★ 理解成:src/ 里的内容,平铺进 dst。

# === ★ 源【不带】斜杠 = 把"这个目录本身" ===
$ rsync -av /data/src /dst/
# ★ 把 src 这个目录【本身】,放进 dst。
#   结果:/dst/src/file1  /dst/src/file2 ...
# ★ 理解成:src 这个目录,整个搬进 dst -> 多一层 src。

# === ★ 目标路径的斜杠,无所谓 ===
# /dst 和 /dst/ 对 rsync 来说一样。只有【源】的
#   末尾斜杠,会改变行为。盯紧源就行。

# === ★ 一句话记牢 ===
# ★ 源带斜杠 = "把里面的东西";源不带 = "把这个目录"。

# === ★ 斜杠写错,叠加 --delete,就是双重灾难 ===
# 假设你本想镜像内容,却漏了源的斜杠:
$ rsync -av --delete /data/src /backup/   # ★ 漏了 src 后的 /
# ★ rsync 认为目标该有的,是"src 这个目录" ->
#   /backup/ 里除了 src 之外的一切,都成了"源里没有
#   的" -> --delete 全删!同时文件又套进了 /backup/src/。
# ★ 套娃 + 误删,一起发作。--delete 的命令,斜杠
#   必须【看三遍】再回车。

# === 认知 ===
# ★ rsync 源路径末尾的斜杠决定行为:src/ 带斜杠是
#   "把目录里的内容"平铺到目标,src 不带斜杠是"把
#   目录本身"放进目标(目标里多一层 src)。目标路径
#   的斜杠无所谓。带 --delete 时斜杠写错 = 套娃叠加
#   误删,务必看清再回车。

修复 3:--delete 是把双刃剑——它删的不是"你删过的"

# === ★ 本文最核心的认知:--delete 到底删什么 ===

# === ★ 我当初对 --delete 的理解(错的) ===
# 我以为:--delete 删的,是"我在源里【删掉过】的那
#   几个文件" —— 我源里删了 a.sql,它就帮我把目标的
#   a.sql 也删掉。听起来很贴心。
# ★ 这个理解,危险地错了。

# === ★ --delete 的真实定义 ===
# ★ --delete 删的是:【源里此刻没有、而目标里有】的
#   一切东西。
# ★ 注意:它根本【不知道】"你删过什么"。它没有历史、
#   没有记忆。它只做一件事 —— 把源和目标摆在一起比,
#   目标里多出来的,删。

# === ★ 两种说法,在"源完整"时等价,出事时天差地别 ===
# - "删我删过的文件"  —— 我以为的
# - "删源里没有的东西" —— 实际的
# ★ 当源是完整的:我删过的,正好就是"源里没有的"
#   那几个 -> 两种说法,结果一样 -> 所以平时看不出
#   区别,--delete 用起来很"乖"。
# ★ 当源出了问题(比如整个空了):"源里没有的东西"
#   = 【目标里的全部】-> --delete 把目标全删 ->
#   这时才暴露:它从来不是"删你删过的"。

# === ★ 为什么 --delete 又不能不用 ===
# 那不用 --delete 不就安全了?—— 不行,会有别的问题:
# ★ 不带 --delete,目标只增不删 -> 你在源里早就删掉
#   的过期文件、垃圾文件,会【永远】堆在备份里,越积
#   越多,而且"备份"和"源"会渐渐不一致。
# ★ 所以 --delete 是"严格镜像"必需的。问题不在于
#   用不用它,在于 ★ 用它之前,必须确保"源是可信的"。

# === ★ --delete 还有几个变体,顺带认一下 ===
$ rsync -av --delete ...              # 默认:边传边删
$ rsync -av --delete-after ...        # 传完所有文件后,最后才删
$ rsync -av --delete --max-delete=20 ...  # ★ 删超过 20 个就中止
# ★ --max-delete 是个救命参数,修复 5 细讲。

# === 认知 ===
# ★ --delete 删的【不是】"你删过的文件",而是"源里
#   此刻没有、目标里有"的一切 —— 它没有历史、没有
#   记忆,只比对源和目标当下的差异。源完整时这和
#   "删你删过的"恰好等价,所以平时很乖;源一旦出
#   问题,它就会把目标里的一切都当成"该删的"。

修复 4:事故根因——源目录消失,rsync 把"空"当成了真相

# === ★ 把本文事故的因果链,一环一环钉死 ===

# === ★ 第一环:/mnt/databak 是个"挂载点" ===
# /mnt/databak 这个路径,平时 ls 出来的几百个文件,
#   ★ 其实不在根文件系统上 —— 它们在数据盘 vdb 里。
#   vdb 这块盘,被【挂载】到了 /mnt/databak 这个点。
$ df -h /mnt/databak
Filesystem      Size  ...  Mounted on
/dev/vdb        50G   ...  /mnt/databak       # ★ 盘挂在这

# === ★ 第二环:盘没挂上,挂载点退回成空目录 ===
# 某次重启后,vdb 因为某种原因【没有自动挂载】——
#   可能 /etc/fstab 漏写了它,可能盘临时故障。
# ★ 这时,/mnt/databak 这个目录【还在】,但它退回成
#   了【根文件系统上一个普普通通的空目录】—— 盘里
#   那几百个文件,一个都不在它下面了。
$ mountpoint /mnt/databak
/mnt/databak is not a mountpoint               # ★ 它现在只是个空目录

# === ★ 第三环:rsync 看源,看到的是"空" ===
# cron 半夜照常跑 rsync。rsync 去读源 /mnt/databak/,
#   它【看到的就是空的】。
# ★ 关键:rsync 【没有任何办法】知道"这个目录本该
#   有几百个文件,只是盘没挂"。它没有这种记忆,也
#   没有这种判断力。源此刻是空 -> 对它而言,源就是空。

# === ★ 第四环:--delete 忠实地把"空"同步过去 ===
# rsync 的逻辑(修复 1):让目标 = 源。源是空的 ->
#   目标也必须是空的 -> --delete 把 /backup/db/ 里
#   几百个文件,一个不留,全删。
# ★ rsync 全程【没有报错】,因为它【没有出错】——
#   它百分之百正确地执行了"把目标同步成和源一致"。

# === ★ 所以,错的到底是什么 ===
# ★ 不是 rsync 的 bug。是我们,把一条"严格镜像"的
#   命令,建立在一个【没有验证过的前提】上 ——
#   "/mnt/databak 此刻,真的是那个装着数据的盘"。
# ★ 这个前提,大半年里一直成立,所以脚本一直"正常"。
#   它一旦不成立(盘没挂),命令的含义就从"备份"
#   翻转成了"清空"。

# === ★ 教训:跑 rsync 前,必须验证"源是真的" ===
# 对一个【挂载点】做备份源,跑 rsync 之前,必须先
#   确认那块盘【真的挂载着】:
$ mountpoint -q /mnt/databak && echo "已挂载,可以备份"
$ findmnt /mnt/databak                # 另一种确认方式
# ★ "源非空 / 源已挂载",是 --delete 备份脚本的
#   【前置生命线】—— 修复 5 把它做成代码。

# === 认知 ===
# ★ 事故根因:/mnt/databak 是数据盘的挂载点,盘没挂
#   载时它退回成根文件系统上的空目录;rsync 没有
#   "源本该有几百个文件"的记忆,只认源此刻是空,于是
#   --delete 把目标也清空。rsync 没有 bug,错在把
#   "严格镜像"命令建立在"源一定可信"这个没验证的
#   前提上。跑 rsync 前必须先确认源真的挂载、非空。

修复 5:给 --delete 上保险——dry-run、挂载检查、max-delete

# === ★ 怎么让"严格镜像"不再变成"误删利器" ===

# === ★ 保险 1:--dry-run,先空跑一遍看它要干什么 ===
# ★ --dry-run(简写 -n):rsync 把"会做的动作"全
#   显示出来,但【一个文件都不真动】。
$ rsync -avn --delete /mnt/databak/ /backup/db/
# ★ 任何带 --delete 的命令,★ 第一次跑、改过之后跑,
#   都【先加 -n 空跑】。看清楚它要删什么,再去掉 -n。
# ★ 本文事故,如果当晚是人在 -n 模式下跑,一眼就能
#   看见满屏 deleting,绝不会回车。

# === ★ 保险 2:-i 逐项显示,看清每个文件的动作 ===
$ rsync -avni --delete /mnt/databak/ /backup/db/
*deleting db_2023.sql                # ★ 行首 *deleting = 要删它
*deleting db_users.sql
>f+++++++++ newfile.sql              # >f 开头 = 要新传
# ★ -i(--itemize-changes)把每个文件的动作列出来。
#   配合 -n,删什么、传什么,一清二楚。

# === ★ 保险 3:源挂载检查 —— 脚本的前置生命线 ===
# ★ 备份脚本里,跑 rsync 之前,先确认源真的挂着:
#!/bin/bash
set -euo pipefail                    # ★ 见保险 5
SRC=/mnt/databak
DST=/backup/db
# ★ 源不是挂载点 -> 立刻退出,绝不往下跑 rsync
mountpoint -q "$SRC" || { echo "源未挂载,中止备份"; exit 1; }
# ★ 再加一道:源是空的也中止(双保险)
[ -n "$(ls -A "$SRC")" ] || { echo "源为空,中止备份"; exit 1; }
rsync -av --delete "$SRC/" "$DST/"

# === ★ 保险 4:--max-delete,删太多就踩刹车 ===
$ rsync -av --delete --max-delete=20 /mnt/databak/ /backup/db/
# ★ --max-delete=N:这次同步,如果要删的文件【超过
#   N 个】,rsync 就【中止】,不删了。
# ★ 它是一根"保险丝":正常增量备份,一次删几个文件
#   很合理;一次要删几百个 —— 那一定出事了。本文
#   场景,只要当初加了 --max-delete=20,几百个文件
#   的删除会被直接拦下。★ 强烈建议给 --delete 配它。

# === ★ 保险 5:脚本里别让变量为空把路径变成 / ===
# ★ 一个经典惨案:脚本里 SRC 变量没赋上值(空):
$ SRC=""
$ rsync -av --delete "$SRC/" /backup/db/    # 变成 rsync ... "/" ...
# ★ "$SRC/" 展开成 "/" —— 你在拿【整个根文件系统】
#   当源!后果不堪设想。
# ★ 防线:脚本开头 set -u(变量未定义就报错退出),
#   或 set -euo pipefail;路径变量永远加引号。

# === ★ 保险 6:最重要的 —— 镜像不是备份 ===
# ★ --delete 镜像,会把"源的损坏"忠实同步给目标:
#   源被误删/被加密勒索/被改坏 -> 下次 rsync ->
#   目标也跟着坏。镜像【只有一份当下】,没有历史。
# ★ 真备份要有【多个时间点】:用 rsync 的 --link-dest
#   做多版本快照,或上专门的备份方案。一份会自动
#   跟随源的镜像,永远不能当成你唯一的备份。

# === 认知 ===
# ★ 给 --delete 上保险:先 --dry-run(-n)空跑、加
#   -i 看清每个文件动作;脚本里跑 rsync 前用
#   mountpoint -q 确认源已挂载、ls -A 确认源非空;
#   配 --max-delete=N 当保险丝删太多就中止;set -u
#   防变量为空把路径变成根。最根本的:--delete 镜像
#   不是备份,它没有历史,真备份要留多个时间点。

修复 6:rsync 备份排查纪律

# === 这次事故暴露的认知盲区,定几条纪律 ===

# === 1. ★ rsync 带 --delete 是"镜像",让目标和源一模一样,不是 cp 式叠加 ===

# === 2. ★ rsync 永远以源为唯一真相,目标该长什么样完全由源此刻决定 ===

# === 3. ★ 源路径末尾斜杠:src/ 拷内容,src 拷目录本身,带 --delete 时必须看清 ===

# === 4. ★ --delete 删的是"源里此刻没有、目标里有"的一切,不是"你删过的" ===

# === 5. ★ 源是挂载点时,盘没挂载它就退回成空目录,rsync 会把"空"同步过去 ===

# === 6. 跑 --delete 前先 mountpoint -q 确认源已挂载、ls -A 确认源非空 ===
$ mountpoint -q /mnt/databak || exit 1

# === 7. ★ 任何 --delete 命令先用 -n(--dry-run)空跑,看清要删什么再去掉 -n ===
$ rsync -avn --delete 源/ 目标/

# === 8. 给 --delete 配 --max-delete=N,一次要删太多就中止当保险丝 ===

# === 9. ★ 脚本里路径变量加引号 + set -u,防变量为空把源变成 / ===

# === 10. 排查"rsync 把数据删了"的步骤链 ===
$ grep deleting /var/log/backup.log  # ① 确认它确实在删
$ ls -A 源目录                       # ② 源现在是空的吗
$ mountpoint 源目录                  # ③ 源那块盘还挂着吗
$ findmnt / lsblk                    # ④ 盘在不在 挂哪了
# 源空了/没挂 -> 先修挂载,再谈恢复;★ 镜像不是备份,
# 要恢复得靠独立的多时间点备份。按此顺序能定位根因。

命令速查

需求                        命令
=============================================================
镜像同步(只增不删)        rsync -av 源/ 目标/
镜像同步(严格 会删)       rsync -av --delete 源/ 目标/
空跑预览不真改              rsync -avn --delete 源/ 目标/
逐项显示每个文件动作        rsync -avni --delete 源/ 目标/
删超过 N 个就中止           rsync -av --delete --max-delete=20 ...
传完再统一删                rsync -av --delete-after 源/ 目标/
查目录是不是挂载点          mountpoint /mnt/databak
查挂载详情                  findmnt /mnt/databak
查盘和挂载关系              lsblk / df -h
多时间点快照备份            rsync -av --link-dest=上次备份 源/ 新目标/

口诀:rsync 带 delete 是镜像 让目标等于源 源说了算不是 cp 叠加
      源带斜杠拷内容 delete 删的是源里没有的 跑前先 mountpoint 确认源真挂着

避坑清单

  1. rsync 带 --delete 是镜像同步让目标和源一模一样,不是 cp 那种把源叠加到目标
  2. rsync 永远以源为唯一真相,目标原来有什么多重要它一概不管,目标该是什么样完全由源此刻决定
  3. 源路径末尾带斜杠是拷目录里的内容,不带斜杠是拷目录本身目标里会多一层,带 --delete 时务必看清
  4. --delete 删的是源里此刻没有而目标里有的一切,它没有记忆不是删你删过的那几个文件
  5. 源完整时删源里没有的和删你删过的恰好等价所以平时很乖,源一出问题就暴露它会删光目标
  6. 源目录是挂载点时那块盘没挂载它就退回成空目录,rsync 看到空就会把空忠实同步给目标
  7. 跑 --delete 备份前必须先 mountpoint -q 确认源已挂载,再 ls -A 确认源非空,两道都不过就中止
  8. 任何 --delete 命令先加 -n 即 --dry-run 空跑一遍,看清楚它要删什么再去掉 -n 真跑
  9. 给 --delete 配 --max-delete=N 当保险丝,一次要删的文件超过 N 个就中止不再删
  10. --delete 镜像会把源的损坏一起同步过去它没有历史不是真备份,真备份要留多个时间点的快照

总结

这次"备份脚本亲手删掉我的备份"的事故,纠正了我一个关于"备份"的、近乎信仰的错觉。在我过去的脑子里,"备份"这两个字,天然带着一种【方向】——它是一个只进不出的保险箱。我往里放东西,它就替我保管;它的全部职责,就是"留住"。我从没想过,一个名字里写着"备份"的东西,会朝着相反的方向用力,会【主动地、成功地】把我存进去的东西清空。所以那天我打开空荡荡的 /backup/db/,我的第一反应不是"哪里出错了",而是一种近乎荒诞的错愕:这就像我打开保险箱,发现保险箱不仅空了,它的日志还得意地告诉我,是它自己昨晚把东西一件件搬出去扔了的。现场逼着我承认一件我从没正视过的事:我那行 rsync --delete,它根本就不是一个"备份"命令。它是一个"让两个地方变得一模一样"的命令——一个【镜像】命令。镜像,是没有方向的。它不区分"源"和"备份",在它眼里,只有"基准"和"跟随者"。它的职责不是"留住"任何东西,而是"忠实地复制基准此刻的样子"。基准里有,它就让跟随者也有;基准里【没有】,它就让跟随者也【没有】——哪怕"没有",是因为基准自己出了事。我一直以为我给它的任务是"保护数据",其实我给它的任务,从头到尾都是"服从源"。大半年里,源一直是对的,服从源和保护数据,看上去是同一件事;直到那一夜,源空了,这两件事,彻底分道扬镳。复盘到根上我才看清,我把全部的信任,押在了一个我从未验证过的前提上——"/mnt/databak 里,永远装着我的数据"。这个前提,不是 rsync 保证的,也不是任何人保证的,它只是【碰巧】成立了大半年。而 rsync 这个工具,它最"忠诚"、也最"无情"的地方就在于:它会把它看到的现状,不打折扣地、不带怀疑地,变成结果。它没有常识,不会想"一个备份源怎么会突然空了,是不是哪里不对";它只会说"源是空的,好,那我照办"。它的忠诚,恰恰要求【我】替它保留那份怀疑。这次最大的收获,是我对一切"会自动跟随某个基准"的机制,生出了一层很深的戒备。一个会自动同步的东西,它的安全,从来不取决于它自己有多可靠,而完全取决于【它所跟随的那个基准,是不是可信】。自动化越彻底、执行越忠实,这件事就越危险——因为它会把基准的错误,用和复制正确时一模一样的效率和忠诚,放大、固化、传播下去。一个会自动拉取配置的服务,配置源被改错了,它就忠实地把错误配置铺满整个集群;一个会自动跟随主库的从库,主库被误删了,它就忠实地把删除同步下来。"自动"省掉的是人的操作,但它【省不掉、也不该省掉】的,是"在动手之前,先确认基准本身是对的"这一步。所以下一次,当我要搭建任何一个"它会自己跟着某个源走"的流程时,我不会再只盯着"同步得快不快、准不准"。我会先停下来,死死盯住那个源,问一个最朴素的问题:在让任何东西去忠实地跟随它之前——我,凭什么,确信它此刻是对的?我会给这个流程,装上一道它自己永远不会有的东西:一道在动手前检查"基准是否可信"的关卡,一根"改动大到不正常就立刻刹车"的保险丝。因为一个忠实的执行者,你给它一个错的前提,它会用尽全力,把这个错误,办得无比成功。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

文件中文全是乱码:一次 Linux 字符编码与字节转换的排查复盘

2026-5-21 0:33:24

Linux教程

为了连数据库我把端口开到公网:一次 SSH 端口转发该早点学会的复盘

2026-5-21 0:42:53

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索