内容改完發布,自己刷新一看還是舊版本;再過一天去看,蜘蛛快照里也還是老样子。遇到這種情况,先別急着怀疑程序寫错了,多數时候是缓存没有跟着發布動作一起更新。缓存本身是必要的,問题在于“發布”和“刷新”被拆成了两件事。
缓存通常不止一层
一個頁面從服務器到訪客屏幕,中間可能经過好几层缓存,改了一层不代表全都生效:
- 浏览器缓存:由 Cache-Control、Expires 控制,訪客本机留存的副本,服務器刷新了它也不會自動失效。
- CDN 邊缘节点:每個节点各存一份,回源一次後在 TTL 内一直用自己那份。
- 反向代理或頁面缓存:Nginx、Varnish 之類的整頁缓存,缓存键设計不好时容易串頁。
- 應用层缓存:對象缓存、查询缓存、模板编译缓存,改資料库不一定改缓存。
所以“我明明改了”這句话要先落到具体一层上,否則排查會一直在原地打轉。
先判断看到的是哪一层的舊内容
命令行請求一次首頁或内容頁,重点看响應头:
- 多次請求同一地址,观察 Age 是否递增,递增說明命中了中間层缓存。
- 看 X-Cache、CF-Cache-Status、X-Cache-Hits 這類字段,判断是 HIT 還是 MISS。
- 带一個随机查询參數再請求一次,如果内容變新,基本可以确定問题在按完整 URL 缓存的那一层。
- 對比登入態和未登入狀態,確認有没有把個性化内容缓存成了公共頁面。
浏览器自身還有一层本地缓存,直接刷新未必能绕過。判断之前先開無痕窗口或强制刷新,避免把本地副本当成服務器狀態。
把刷新動作寫進發布流程
靠人工想起来点一下刷新按钮,迟早會漏。更稳的做法是把步骤固定下来:
- 儲存發布後,触發對應范围的刷新:單 URL、栏目目錄或按标簽批量刷新。
- 等几秒,让各個邊缘节点收到失效通知。
- 用命令行請求目标地址,確認返回的是新内容而不是 HIT 狀態。
- 检查静態资源版本号是否更新,避免 HTML 是新的、CSS 還是舊的。
- 在發布记錄里留一條時間戳,後續出問题能對照。
静態资源建议用带版本或哈希的文件名,HTML 用較短的缓存時間或协商缓存。這样每次發版天然就是新地址,不必依赖全站清理。
缓存键和參數容易踩的坑
有的配置把完整 URL 连同參數一起当缓存键,同一個頁面带上不同跟踪參數就各存一份,既占空間又容易内容不一致;反過来,如果配置忽略參數,又可能把两個不同内容的頁面缓存成同一份。
做了移動端和桌面端分發的站点,要核對 Vary 头。用 User-Agent 做区分會把缓存打散成很多份,命中率下降;用一套响應式頁面則可以省掉這個問题。带登入態、带個性化推荐的区块,不要走整頁缓存,或者把它放到頁面加载後再取。
有些地方不该设長缓存
- 後台、草稿頁、预览頁:應当明确不缓存,避免修改後長期看不到變化。
- 带临时令牌的分享連結:缓存後可能被無關訪客命中。
- 错誤响應:404 和 5xx 不要缓存,否則問题修好了,訪客看到的還是错誤頁。
- robots.txt 和站点地图:建议短缓存,規則調整後能較快生效。
定期抽查,比事後猜更省事
挑出首頁、几個主要栏目頁和若干内容頁,每周固定請求一次,记錄缓存命中狀態和刷新耗时。如果某次刷新後長時間仍是舊内容,說明失效通知没送達,或者缓存键设計有問题,這时候再去調配置才有依據。
缓存不是配置一次就不用管的東西。把“更新—刷新—驗證”当成一個完整動作,能省掉大量“到底是没更新還是没刷新”的来回確認。