缓存是个容易被忽略的环节。配得合适,老访客第二次打开页面时,浏览器和 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。
缓存不是“设了就好”,而是要和发布流程配套。设成什么时间、什么时候失效、由谁负责清,这三件事最好写进操作记录里,下次改版时才不用重新猜。
最后提醒一句,缓存策略没有通用最优解。新闻列表页和产品详情页的更新频率不同,登录后的接口和公开静态资源也不该共用一套规则。先把页面按更新频率分个类,再分别配置,比整站套一个模板要省事得多。