如何校验 MD5?

解读

在国内 CouchDB 面试中,面试官问“如何校验 MD5”并不是想听你背诵 Linux 命令,而是考察三层能力:

  1. 你是否理解 CouchDB 对 附件完整性 的依赖——CouchDB 把每个附件的 MD5 摘要 作为 ETag 返回,并在复制时用它做 增量差异比对
  2. 你是否能在 离线优先、弱网、移动同步 场景下,用最小成本验证文件未被篡改或损坏;
  3. 你是否知道 国内合规要求(等保 2.0、国密改造)对摘要算法的潜在影响,能否给出可落地的替代或加固方案。
    因此,回答必须围绕“CouchDB 原生机制 → 客户端校验 → 运维闭环 → 合规演进”展开,体现端到端的工程思维。

知识点

  1. CouchDB 附件的 content-md5 头部与 ETag 语义一致,均存放 Base64 编码的 MD5 值。
  2. 增量复制 时,源库会在 multipart/related 响应里携带 Content-MD5,目标库会 二次计算 并比对,失败则回滚该附件。
  3. 国内移动网络 高丢包、高延迟,客户端必须先做 本地 MD5 预校验,再决定是否上传,节省 30%~50% 流量。
  4. 等保 2.0 对 数据完整性 有检查点,若未来需改用 SM3,CouchDB 可通过 自定义中间层before_doc_update 钩子中二次摘要,实现 双摘要并存,平滑过渡。
  5. 大文件场景下,流式 MD5 比一次性加载内存更安全,Node.js 使用 crypto.createHash('md5'),Java 使用 DigestInputStream,防止 OOM 导致容器重启。

答案

CouchDB 内置了附件级 MD5 校验,分为 服务端、客户端、运维 三条线:

  1. 服务端:

    • 上传附件时,CouchDB 自动计算 MD5 并写入 ETag
    • 复制同步阶段,目标节点会 二次计算 收到的字节流,与 Content-MD5 头部比对,不一致则 回滚该附件 并记录 attachment_verify_failurecouch.log,保证 最终一致性
  2. 客户端(以国内最常见的 React-Native + PouchDB 为例):

    • 离线场景先本地计算 SparkMD5,与待上传文件的摘要比对,防止 重复上传
    • 收到 CouchDB 返回的 201 响应后,再次比对 ETag 与本地值,若不一致立即 触发重试 并上报 Sentry,避免用户看到“假成功”。
  3. 运维闭环:

    • 每天凌晨用 couch-dump 导出附件清单,通过 curl -I 批量抓取 ETag,与对象存储(如 阿里云 OSS)的 Content-MD5 交叉校验,差异超过 1‰ 自动发 钉钉告警
    • 若发现不一致,立即触发 couchdb-compact 并对比 before/after 摘要,确认是 位翻转 还是 逻辑损坏,再决定是 单节点恢复 还是 全集群重同步

一句话总结:利用 CouchDB 原生 ETag,结合客户端预校验与运维交叉抽检,形成“上传-复制-巡检”三级 MD5 闭环,既满足高可用,又符合国内等保合规。

拓展思考

  1. 当单附件大于 500 MB 时,MD5 计算会 阻塞 Erlang 调度器,可在 local.ini 里开启 {attachment_buffer_size, 1048576},并改用 NIF 异步摘要,将 CPU 抢占降到 5% 以下。
  2. 国内金融客户已要求 SM3 摘要,可在 反向代理层(OpenResty)lua-resty-sm3 计算双摘要,回写自定义头部 X-SM3,CouchDB 仍存 MD5,实现 双轨制,无需改源码即可过审。
  3. 未来 CouchDB 4.x 若引入 分片附件,MD5 校验粒度将从“整文件”变为 分片级 Merkle Tree,需要提前设计 分片摘要缓存 策略,避免 二次读取 IO 翻倍。