旧服务器已经下架了,怎么证明数据一份没丢——核痕 KeHen-KeHen

本系列:把这两年用 Python 写的十几个小项目挨个开源、挨个说清楚。不讲”我做了什么”,讲”做的过程中被什么打脸、最后怎么想明白的”。 第 11 篇 · 核痕 KeHen(上一篇:烽燧 FengSui) 源码:github.com/ghostgorge/KeHen 纯 Python 标准库,零第三方依赖,Windows / macOS / Linux,单文件 exe


一、现有工具有一个共同的前提

把 800GB 图纸从旧服务器拷到新服务器,怎么确认真的一份没少、一份没坏

robocopy /Lrsync -n、FreeFileSync、Beyond Compare——这些工具都很好用,但它们有一个共同前提:两端同时在线,能同时访问 A 和 B。

而现实里经常做不到:

  • 旧机已经下架了
  • 盘已经寄走了
  • 要比的是”半年前的它“和”今天的它”——A 端根本不存在于当下

核痕换了个做法:把 A 的状态冻结成一个文件。

旧服务器 ──扫描──> 图纸-旧机.dsnap ──U盘拷走──> 新服务器 ──核对──> 报告

快照压缩后大约每个文件 34 字节。600 万个文件的文件服务器,快照约 230MB,随便放。

二、它能看见别的工具看不见的一类问题

大小没变、时间没变、内容坏了。

硬盘静默损坏、bit rot、传输截断补零——这类问题在任何”比大小 + 比时间”的工具眼里都是完全正常的。

只有真的把每个字节读一遍算过指纹,才能看见。

这也是为什么快照默认是 full 模式:

模式读多少能发现静默损坏适合
full(默认)每个字节图纸、文档、代码、照片
sample大于 16MB 的文件只读首/中/尾各 4MB大部分几 TB 的视频/素材盘
none只记大小和时间粗略清点

三、最危险的一类 bug:它不会崩溃,只会悄悄说”一致”

这一节是整篇文章我最想让人看到的。

为了让内存与目录规模无关,快照按一个约定顺序写入:每层目录内按 casefold 排序,遇到子目录先输出自己再立刻递归。这个顺序恰好等于”按路径分量元组排序“,于是比对两份快照就是一次归并推进——600 万文件和 6 千文件占的内存一样多。

整个设计建立在这个排序不变量上。而它有一个陷阱:

普通字符串比较会得出 a.txt < a/b,因为 . 的码位(0x2E)小于 /(0x2F)。 必须按路径分量比,不能按整串比。

如果这条不变量破了会怎样?

归并不会崩溃。它会错位,然后给出一份错误的”一致”报告。

你拿着这份报告去确认 800GB 图纸迁移完成、格式化了旧盘——半年后才发现少了一批文件。

所以 selftest.py 的第一项就是专门撞这个不变量的:用随机生成的病态文件名反复验证排序键

一个”失败时会静默给出错误的正确答案”的 bug,比一个会崩溃的 bug 危险一个数量级。 崩溃至少会告诉你出事了。

(这和立方之蛇那篇里”D 键在屏幕上往左走”是同一类问题:不报错、不崩溃,只是悄悄地错。这类地方必须有专门的测试守着。)

四、几个具体的技术决定

用 blake2b(128位),不用 sha256

标准库自带(不引依赖、不多带 DLL)、比 sha256 快、128 位对”检测意外损坏”完全够用。

但要说清楚:这是完整性校验,不是防篡改。有人蓄意构造碰撞的场景请用 sha256。工具的能力边界要写在文档里,不能让用户自己猜。

哈希走线程池

hashlib 读大块数据时会释放 GIL,所以这里多线程是真并行——不是 Python 里常见的那种假并行。

而且瓶颈本来就在磁盘 IO,解释器开销被完全掩盖。实测 2 万文件 / 238MB 全文校验 1.2 秒

快照格式是 gzip 压缩的 JSON Lines

不是自定义二进制,也不是 sqlite。理由:

十年后你还能 gzip -dc x.dsnap | head 直接看懂它,也能拿 Python 或 jq 自己二次加工。

体积代价约三成,压缩后完全可接受。一个用来做长期存证的文件,可读性比省那三成体积重要得多。

“仅时间不同”默认不算差异

跨文件系统拷贝几乎必然改修改时间:FAT32 只有 2 秒精度,SMB、云盘同理。

如果把时间差异也报出来,报告里会全是噪音,跟没有报告一样。需要的话加 --show-time

(这和上一篇烽燧是同一条思路:工具必须自己承担”什么算噪音”的判断,不能原封不动推回给人。)

CSV 一律写 UTF-8 BOM

不带 BOM 的话 Excel 按 GBK 解释,中文路径直接乱码——这是国内用户最常见的”你的工具有 bug”投诉,而它根本不是 bug。

五、三个版本更新,三个”不像它自己”的故事

1.0.1:自测在 Windows 上必然崩

selftest.py 里有一项用了一个含英文双引号的文件名。这在 POSIX 上完全合法,在 Windows 上是保留字符

于是 Windows 下自测必崩在这一项,而 build.bat 是”自测通过才打包”的——整个构建被一个测试用例的文件名卡死了

修法是加一个 portable_name():过滤保留字符、结尾的点和空格、CON/COM1 这类保留设备名,两个平台都合法但仍然足够刁钻。

同版本还修了另一个更严重的:Windows 上超过 MAX_PATH(260) 的深路径会被记成”读取失败”,导致快照残缺。遍历时改为自动加 \\?\ 前缀(UNC 路径走 \\?\UNC\),上限 32767 字符,不需要用户去改注册表的 LongPathsEnabled。新增的测试会真的造一条 350+ 字符的路径来验。

1.0.2:两个 exe 名字只差大小写

KeHen.exekehen.exe 在 Windows 上是同一个文件,PyInstaller 会先生成一个、再把另一个覆盖掉。改成 kehen-cli.exe

(同一个坑我在烽燧里也踩过——两个项目的产物结构一样,坑就一样。)

1.0.3:一个看着完全不像权限问题的报错

快照默认保存位置原本是”被扫目录的上一级”。听起来很合理——直到你选了 D:\project,于是快照默认要存到 D:\ 盘符根目录

而 Windows 上这个位置经常写不进去:受控文件夹访问、云盘映射盘、只读卷、组策略……

报出来的错误是:

Errno 22 Invalid argument

这个报错看着完全不像权限问题。 用户会去检查路径拼错没有、检查磁盘满没满,就是想不到是”根目录不让写”。

三处修改:

  1. 默认位置改成”第一个真的写得进去的目录“(上一级 → 被扫目录本身 → 文档 → 主目录)
  2. 开始扫描前先做一次可写性预检——扫描可能要几十分钟,等跑完才发现存不下是最糟糕的失败方式
  3. 写入失败不再甩 traceback,而是给出能照着做的说明:哪个目录、系统怎么说、换哪里存

第 2 条是这三条里最普适的:任何长耗时任务,都要在开始前把”结束时才会用到的前提”先验一遍。

六、典型用法

场景怎么做
换电脑 / 换硬盘 / 迁 NAS旧机 scan,新机 verify
服务器迁移、机房搬迁迁移前 scan,迁移后 verify --html 报告.html 存档
备份是否可信对备份还原出来的目录跑 verify
移动硬盘有没有悄悄坏每半年 verify 一次,对比同一份快照
素材/图纸交付给客户随交付附一份 .dsnap,对方自己核
装软件前后系统改了什么装之前 scan,装之后 verify
顺手清理重复文件扫描已经算过指纹,dupes 不用再读盘

最后一条是白捡的:既然全文哈希都算过了,查重就是零额外 IO 的副产品。

kehen scan D:\项目图纸 -o 图纸-旧机.dsnap
kehen verify 图纸-旧机.dsnap --root E:\项目图纸 --html 核对报告.html
kehen diff 旧.dsnap 新.dsnap --csv 差异.csv
kehen dupes 图纸-旧机.dsnap --min-size 5MB --html 重复.html

退出码:0 一致 · 1 发现内容变化/缺失/大小不符 · 2 出错。可以直接接进计划任务。

窗口版三页,从左到右用:扫描 → 核对 → 查重

七、已知边界(照实写)

  • 不做同步,只报告差异,不动你任何一个文件。要同步用 FreeFileSync / robocopy。
  • 不判断”哪份是原件”。查重只告诉你哪些文件内容相同,删哪份你自己决定。
  • sample 模式下超大文件中段被改写有极小概率漏检。要确定结论就用 full
  • 软链接/联接点默认记成 0 字节文件、不跟进去(避免成环和重复计数)。
  • ACL、备用数据流、稀疏文件属性不在比对范围内。核痕比的是”文件内容与基本元数据”。
  • 一次比对里若有超过 20 万条新增+缺失,会自动放弃”移动/改名识别”以控制内存,并在报告里写明
python selftest.py     # 42 项自测,全绿再往下走
python main.py         # 窗口版
双击 build.bat         # 先跑自测,通过才打包

尾巴

两条带得走的:

一、当所有现成方案都有同一个前提时,先问这个前提是不是必需的。 同步类工具全都要求两端在线——因为它们要”改”。而只想”证明”的话,一份指纹文件就够了,A 端可以已经不存在。

二、会静默给出错误正确答案的 bug,必须有专门的测试守着。 排序不变量一旦破了,归并不崩溃,只是给你一份错误的”一致”报告——而你会拿着它去格式化旧盘。

📦 源码https://github.com/ghostgorge/KeHen


下一篇:奥赛题库 OlympiadMath——小初高数学奥赛题库桌面程序。回复「继续」。