缓存是個容易被忽略的环节。配得合适,老訪客第二次打開頁面时,浏览器和 CDN 手里已经有大部分资源,只去服務器取真正變化的東西;配得不合适,要么每次訪問都重新下载几百 KB,要么文件明明改了,訪客和蜘蛛拿到的還是几個月前的舊版本。
先分清几個缓存层
一個頁面從服務器到訪客屏幕,中間至少要過三道缓存:浏览器本地缓存、CDN 或反向代理缓存、源站自身的缓存(比如 PHP 的 OPcache、對象缓存)。多數站点的問题出在前两层,因為它們的响應头直接由服務器配置决定。
- 浏览器缓存:由 Cache-Control、Expires、ETag、Last-Modified 控制,决定訪客本地要不要重新發起請求。
- CDN 缓存:决定邊缘节点是否直接返回副本,键值通常是完整 URL 加部分請求头。
- 源站缓存:頁面級缓存、資料库查询缓存,属于後端范畴,配置不当會造成“改了内容頁面不變”。
常见的几處错位
HTML 被设成了長期缓存
给 HTML 設定 Cache-Control: max-age=31536000 是典型的用力過猛。HTML 里往往嵌着目前版本资源的地址,一旦長期缓存,訪客可能半年都看不到新内容。稳一点的做法是让 HTML 走較短的缓存時間,或者用 no-cache 配合 ETag,让浏览器每次都問一句“變了吗”,没變就繼續用本地副本。
静態资源没有文件名指纹
CSS、JS、图片這類文件,如果 URL 固定不變,就没法既给長缓存又能及时更新。常见做法是在文件名或查询串里带上内容哈希,比如 app.3f9a2c.css。文件内容一變,URL 跟着變,缓存自然失效;老的 URL 繼續留在缓存里,也不會影响頁面展示。
ETag 與 Cache-Control 相互打架
有的服務器一邊给出很長的 max-age,一邊又生成 ETag,浏览器在某些情况下仍會發條件請求。更常见的麻烦是多台服務器各自生成的 ETag 不一致,同一個文件在不同节点上“變了又變”,既浪費带宽又影响命中率。如果站点走多机部署,可以把 ETag 關掉,或者统一由 CDN 生成。
CDN 與源站的缓存時間不一致
源站设了十分钟,CDN 设了七天,结果是 CDN 一直在返回舊文件,而源站明明已经更新。排查這類問题时,先看 CDN 控制台的缓存規則,再看源站响應头。两邊取的時間往往以較短的一方為准,但規則優先級各家不同,需要實际抓包確認。
一份可照做的自查清單
- 打開 DevTools 的 Network 面板,對比勾選缓存開關前後的结果,看静態资源的 Size 列是否顯示 from disk cache 或 from memory cache。
- 检查 HTML 响應头,確認没有出現超長的 max-age。
- 检查 CSS、JS、字体、图片的 URL 是否带版本号或哈希。
- 检查 CDN 控制台里的缓存規則,確認没有把整站设成“忽略源站、永久缓存”。
- 用 curl -I 分別請求源站和 CDN 地址,對比 Cache-Control、ETag、Age 三個字段。
- 找一張不常訪問的图片,改個像素重新上传,確認訪客能拿到新版本。
- 检查後台“清除缓存”按钮的實际效果,確認它清的是哪一层。
發版时的顺序
顺序错了,缓存反而會制造問题。比較稳妥的做法是:先上传带新哈希的静態资源,確認可訪問;再更新 HTML 引用;最後按需刷新 CDN 上對應目錄。反過来先清 CDN、再传资源,中間那段時間訪客會拿到 404。
缓存不是“设了就好”,而是要和發布流程配套。设成什么時間、什么时候失效、由谁负责清,這三件事最好寫進操作记錄里,下次改版时才不用重新猜。
最後提醒一句,缓存策略没有通用最優解。新闻列表頁和产品詳情頁的更新频率不同,登入後的接口和公開静態资源也不该共用一套規則。先把頁面按更新频率分個類,再分別配置,比整站套一個模板要省事得多。