一场关于图片加速的连续剧:我们如何把两个站点从“能访问”修到“真的快”
先说结论:
d3v和aiya现在都能稳定走 Cloudflare + R2 链路。- 旧图、新图都能打开。
- 本地历史上传可备份后清理,不再被磁盘绑架。
再说过程:
第一幕:以为是网络问题,结果是“系统性绕路”
一开始大家都盯着延迟看:
- 中国到新加坡,
ping看起来 300ms。 - SSH 卡到怀疑人生。
- 业务站点因为用了 CF 反代和优选域名,体感还行。
于是灵魂一问就来了:
“SSH 能不能也这样加速?”
答案是:能折腾,但不是你给 HTTP 站点加速那套就能直接平移。后面我们把重点先收敛到图片链路,因为这才是用户可感知的大头。
第二幕:404 不是对象没了,而是回源回错家了
最经典的一坑:
- 链接
uploads.aiya.de5.net/...打开是404。 - 但去 R2 里
head_object,对象明明在。
这说明什么?
说明“存储层没问题,流量层有问题”。
最终定位:
uploads.aiya的 SaaS 自定义主机,源站一度指到了另一个桶的入口。- 回源 Host/SNI 和 Nginx 匹配还出现过错位。
- 再叠加 Cloudflare 把旧
404缓存住,看起来就像“修了也没修”。
一句话总结:
对象在桶里,不等于用户能拿到。
第三幕:你以为是 DNS,实际上是“多层配置联动”
这次链路其实有四层:
- 华为云分线路由(国内/境外)
- Cloudflare SaaS custom hostname
- Nginx 回源编排
- R2 bucket 的 public/managed/custom domain
任何一层“看起来差不多”的配置,都可能把请求导到错误的桶或错误的 Host。
所以我们的修法不是“拍脑袋改一条”,而是:
- 先做备份。
- 再逐层验证(DNS → SaaS → origin → bucket object)。
- 每层都用
curl -I和对象HEAD做事实校验。
第四幕:为什么老帖总爱“看起来没改”
很多人会在这里崩溃:
“我不是都上 S3/R2 了吗?为什么老帖还是 aiya.de5.net/uploads/...?”
因为 Discourse 有两个世界:
raw:帖子原文(常见upload://...)cooked:渲染后的 HTML(img/src/srcset在这里)
posts:rebake 会用 raw 重算 cooked。
所以如果你只改了 cooked,后续 rebake 可能又给你“打回默认写法”。
这不是 R2 失效,是渲染策略的正常行为。
第五幕:这次我们到底做了什么
这次最终落地是:
- 修正
uploads.aiya回源到正确 bucket。 - 清理旧 404 缓存。
- 同步历史缺失对象到 R2。
- 对老帖
cooked批量替换到https://uploads.aiya.de5.net/...(让展示也统一)。
效果:
- 你贴出来的炸图全部恢复。
- 新图、旧图都能命中加速链路。
给后来者的“少走弯路清单”
- 不要先怀疑对象丢失,先做
HEAD object。 404先看缓存状态:cf-cache-status: HIT可能只是旧缓存。- SaaS custom hostname 改源后,务必验证实际 Host 匹配。
- 一次改一层,层层验证,不要多处同时改然后祈祷。
- 先备份再迁移,尤其是
/shared/uploads/*。 - 把“回滚路径”写出来,比“这次终于成功”更重要。
彩蛋:这次最真实的体感
这不是“一个 DNS 记录没填对”的问题。
这是“免费方案拼装出来的半企业架构”:
- 能省钱,能提速,能跑起来;
- 但你要拿出 SRE 的耐心,接受它会在边界条件里教育你。
折腾归折腾,最终是值得的:
- 成本没爆;
- 速度上来了;
- 架构也终于从“能用”变成“可解释、可维护”。
希望这篇能帮后来者少熬几个夜。