记一次愚蠢的缓存危机

问题

今天改了 manifest.json, 推到服务器上后, 一直不生效。看了一下缓存头, Cache-Control: max-age:31536000, must-revalidate, 糟糕。。。

原因

  • 下列所述 "过期" 是指 stale: 客户端综合计算 age + max-age + Date 得出的当前资源是否有效的状态;
  • 源服务器综合考虑 If-Modified-SinceIf-None-Match 后, 回复客户端是否过期 (如果已过期, 会在 body 中返回新的资源; 如果没过期, 就返回 304);
  • 下列所述 "不能" / "必须" / "应该" 都是指服务器如此要求, 但是否执行取决于客户端的实现, 你完全可以自己编译一个浏览器, 忽略请求头, 应该不犯法;
  • no-store 表示不能缓存
  • no-cache 表示可以缓存, 但客户端每次使用都必须与源服务器验证是否有变
  • must-revalidate 表示可以缓存, 但一旦过期, 就不能继续使用该资源, 而是必须与源服务器重新验证才能使用, 如果连不上源服务器, 那你就别用了 (注意, 平时没过期的时候, 客户端是不会去找服务端验证的)
  • stale-while-revalidate 表示即使缓存过期了也还能用, 只要你用了之后重新验证并更新缓存就好了, 但是如果验证出错了 (500 或 404 或其他什么错误), 那你下次就不应该再用那个过期的资源了
  • stale-if-error 表示即使缓存过期了也还能用, 之后重新验证并更新缓存就好了, 万一重新验证出错 (500 或 404 或其他什么错误), 你下次还能用旧的缓存
  • immutable 表示只要还没过期,你就尽管用, 不要来问我服务器, 肯定不会变

不应变化的静态资源可以适用 must-revalidate, 例如不会变化的文件如 vconsole.min.js, 自带版本号的文件如 sensorsdata-20110513.min.js, 这类文件无需打包, 可以直接放在 public 静态目录中, 内容长期不会变化, 如果有变, 我们也会修改版本号, 所以可以适用长期缓存, 且客户端每次使用都无需重新验证。(这种情况其实也能用/更适合用 immutable)

但是像一些可能有变、且不能随便修改文件名的文件, 千万不要使用 must-revalidate (即使要用, 也设一个短一点的 max-age)。如 favicon.ico, manifest.json 等, 应该用 no-cache, 每次用的时候必须验证是否有变。

解决办法

没什么好办法, 只能庆幸这是一个没人访问的私人站点。。。

  1. 手动清理浏览器缓存, 发现还是没用, 然后发现 Response Headers 中有另一个 header: X-Cache: HIT, 所以:

  2. 清理 nginx 缓存

OK, 勉强搞定

后记

最后, 如果真的有用户访问的项目遇到了这样的问题, 我们没办法一个一个去清理用户的浏览器, 那么我们能怎么做呢?

  1. Deleting stored responses 说, 可以给服务器发个同 url 的 POST 请求

    One of the methods mentioned in the specification is to send a request for the same URL with an unsafe method such as POST, but that is usually difficult to intentionally do for many clients.

  2. 同样是 1 中的链接说的, 使用 Clear-Site-Data: "cache" 标头 (但是并非所有浏览器都支持该标头, 且该方法只能清空浏览器缓存, 无法处理中间缓存 intermediate caches)

总结来说, 就是谨慎点, 别搞出这样的事, 给自己找麻烦。

如非特别声明,本站作品均为原创,遵循【自由转载-保持署名-非商用-非衍生 创意共享 3.0 许可证】。

对于转载作品,如需二次转载,请遵循原作许可。