版本号要写进文件名,而不只是写进发布说明
在同一个 URL 上覆盖文件,意味着 CDN 会继续发旧内容好几个小时。如果团队里没人握有清缓存权限的令牌,文件名就是你唯一的缓存键。

版本号要写进文件名,而不只是写进发布说明
"文件已经在桶里、但用户还拿不到"是一类不产生任何报错、日志和构建失败的缺陷。它只是安静地继续把上一个版本发给别人,直到某个东西过期。
我们在把下载迁到 CDN 背后的对象存储时撞上了它,而最终的修法是一条命名约定,不是什么技术手段。
问题的形状
最直觉的布局是每个产物一个固定 URL:
https://dl.example.com/YourApp-Setup-x64.exe
文档好写,支持文章好写,下载按钮也永远不用改。把新构建覆盖上去,所有人就拿到新版本了。
但并不会。CDN 按 max-age 缓存了这个对象,而缓存键是 URL。你换掉了键背后的字节,却没有换键。每个已经持有旧对象的边缘节点,都会继续发它,直到各自的 TTL 走完。max-age 是四小时,就意味着最长四小时里用户下到的是一个你以为已经替换掉的构建 —— 而且各边缘节点是错开的,你自己从某个位置测出来是对的,世界另一半可能还在拿旧文件。
为什么"清一下缓存"经常不可用
标准答案是清缓存。这需要一把在该 zone 上具备 cache-purge 权限的 API 令牌。
在你把方案建立在这个前提上之前,先确认你真的有。我们没有。部署链路里那两把令牌都是按各自用途授权的 —— 一把管 DNS 记录,一把管存储 —— 都不能清缓存。而在发布压力下临时签一把权限更大的令牌、接进 CI、再放到每次发布都够得着的地方,是对安全面的实质改动,只为解决一个本来有免费替代方案的问题。
免费的替代方案就是:别再复用这个键。
带版本号的文件名 + 一个稳定别名
把构建写到带版本号的键上,同时保留裸名作为副本:
YourApp-Setup-x64-1.1.0.exe ← 站点链接指向它
YourApp-Setup-x64.exe ← "latest" 别名,同样的字节
站点的下载配置指向带版本号的名字。新版本就是新键,因而是新的缓存条目,因而部署落地的那一刻就生效 —— 不用清缓存,不用等,也没有各边缘不一致的问题。
裸名要保留,因为链接是会外流的。它散落在旧的发布说明里、论坛帖子里、客服回复里、某个人的书签里。让它继续指向当前字节,这些链接就仍然可用;而它们在几个小时内被缓存服务的事实,在它不是你刚刚昭告天下那条链接的前提下,影响就小得多。
发布顺序很重要,值得写在配置旁边:
- 先上传带版本号的新文件。
- 用同样的字节覆盖裸名别名。
- 把站点改成指向新的版本号文件名。
- 部署。
按这个顺序做,就不存在"站点引用了一个还不存在的键"的窗口。
两件不核实就会搞错的事
先确认后端真的是你以为的那个。 我们的配置已经按"存储迁移已完成"的假设改成了扁平路径。但它没完成:桶建好了、对象也传进去了,却没有绑定自定义域,所以主机名仍然解析到旧的服务商。于是所有新格式 URL 返回 404,所有旧格式返回 200。破绽在 404 的响应体 —— 那是旧服务商的 JSON 错误格式,不是新的 —— 以及 200 响应里带着旧服务商特有的响应头。在相信一条迁移说明之前,先抓一个真实文件看响应头。
刚改完 DNS,你自己的解析器会骗你。 绑定当场就生效了,而我们本机的 curl 又报了八分钟的 Could not resolve host,因为解析器把之前的 NXDOMAIN 负缓存了。这看起来和绑定失败一模一样。下结论之前,直接查权威解析器,或者把地址钉死:
dig +short dl.example.com @1.1.1.1
curl --resolve dl.example.com:443:<ip> https://dl.example.com/file无论如何都该有的那一步检查
不管你用什么命名方案,发布都不算完成 —— 除非你已经从产物的公开 URL 把它抓下来(不是从桶里,不是从签名链接,而是用户会点的那个 URL),并和你构建出来的文件比对过哈希。
curl -sL -o /tmp/dl "$PUBLIC_URL"
shasum -a 256 /tmp/dl build_outputs/TheArtifact两个哈希一致,你就同时知道了上传、绑定、缓存、链接这四件事都是对的。它花一分钟,而且是唯一一个能一次覆盖这四项的检查。
如果这篇只记住一句:文件名就是缓存键。把改文件名当作发布流程的一部分,就像你对待版本号一样。