站点运营

站点运营:静態资源缓存與版本号自查,別让訪客繼續加载舊文件

改版上线後仍然有訪客看到舊样式、舊图片,多半不是代碼没發上去,而是缓存没更新。這篇文章按浏览器、CDN、反向代理、源站的顺序梳理缓存排查思路,說明 Cache-Control、ETag、Expires 几個字段该怎么看,版本号加在哪里才算數,並给出一份可执行的自查清單。

站点运营

站点运营:静態资源缓存與版本号自查,別让訪客繼續加载舊文件

很多人遇到過同一個現象:前端明明改完了,图片也換了,自己在浏览器里清一下缓存就一切正常,可客服還是會隔三差五收到「样式错乱」「图片還是舊的」這類反馈。多數时候並不是文件没發上去,而是浏览器或 CDN 還在用本地缓存里的舊副本。缓存本身是好事,它省带宽、也让二次訪問更快;麻烦在于文件更新了,缓存並不知道。

先分清缓存發生在哪一层

從訪客到源站,中間可能叠了好几层:浏览器缓存、CDN 邊缘节点、反向代理(Nginx、Varnish 之類),最後才轮到應用本身。排查时按「由近到遠」的顺序看,比一上来就重啟服務器有效得多。

  • 浏览器:開發者工具的網絡面板看 Size 一列,出現 memory cache 或 disk cache 就說明命中了本地;
  • CDN:看响應头里的 X-Cache、Age、CF-Cache-Status 之類的字段;
  • 反向代理:看代理层的缓存目錄與訪問日誌;
  • 源站:以上都没命中,請求才會真正打到後端。

把「這次請求停在哪一层」先确定下来,後面的動作才有针對性。

缓存头里真正需要關心的字段

Cache-Control

優先級最高的控制項。max-age 决定文件在缓存里能活多久;no-cache 並不是不缓存,而是每次使用前都要回源校驗;no-store 才是彻底不落盘。對已经带版本号或指纹的静態资源,可以放心设長一些;對 HTML 入口文件,通常设短時間或每次校驗,免得入口被卡在舊版本上。

ETag 與 Last-Modified

這两個是给回源校驗用的。文件没變就返回 304,省下传輸体积。需要留意一種情况:如果同一文件在多台机器上算出的 ETag 不一致(比如集群里拿 inode 当依據),校驗會反复失敗,反而拖慢。遇到這種表現,可以统一 ETag 的生成規則,或者干脆只依赖 Last-Modified。

Expires

属于較早的寫法。它和 Cache-Control 同时存在时容易互相打架,建议只保留一處,並且表意一致,別一邊寫缓存一年、一邊寫不缓存。

版本号加在哪里才算數

常见做法是给文件名或查询串加指纹,例如 app.3f9a2c.js 或 app.js?v=3f9a2c。几個要点值得记一下:

  1. 文件内容没變就不要動版本号,否則每次發布都让全部訪客重新下载一遍;
  2. 入口 HTML 里引用的资源路径要一起改,別只換了资源却漏了引用;
  3. 尽量別拿發布日期或時間戳当版本号,除非你确實每次都有真實變更;
  4. CDN 或對象存储上的舊文件先別急着删,留一段時間给還没刷新的訪客。

一份可以直接照做的自查清單

  • 抽查首頁、栏目頁、詳情頁的 HTML 响應头,確認缓存時間符合预期;
  • 抽查 JS、CSS、图片的响應头,確認是否带長缓存和指纹;
  • 检查有没有同名文件被覆盖、版本号却没變的情况;
  • 確認 CDN 刷新策略:是全量刷新還是按目錄刷新,谁有操作權限;
  • 確認回源时缓存头是否被正确透传,有的代理會顺手改寫;
  • 完整记錄一次「改文件—發布—驗證」,看看各层分別花了多久才更新。

發布节奏上的两個小习惯

一是把 HTML 和静態资源分開對待:HTML 走短缓存,资源走長缓存,這样内容更新能马上生效,资源又不至于反复下载。二是動手前先問一句「這次改的是内容還是文件本身」,如果只是文案微調,通常不需要動版本号,改了反而增加無谓的流量。

缓存問题的排查顺序永遠是:先弄清請求命中了哪一层,再决定清哪一层。盲目清缓存只能解决一时,下次還會遇到。

最後提醒一句,缓存策略不是设一次就完事。換 CDN、調服務器、換构建工具,都可能悄悄改變响應头。把它固定成上线检查里的一個环节,以後能省下不少解释成本。