Skip to content

浏览器缓存

缓存术语

  • 新鲜度检测:检测缓存是否过时;
  • 命中:在缓存中找到数据;
  • 不命中/穿透:在缓存中未找到数据;
  • 命中率:命中次数/总次数;
  • 请求折叠:如果多个相同的请求同时到达共享缓存,中间缓存将代表自己将单个请求转发到源,然后源可以将结果重用于所有客户端;

缓存分类

  • 私有缓存:私有缓存是绑定到特定客户端的缓存——通常是浏览器缓存;
  • 共享缓存:
    • 代理缓存:除了访问控制的功能外,一些代理还实现了缓存以减少网络流量。这通常不由服务开发人员管理,因此必须由恰当的 HTTP 标头等控制;
    • 托管缓存:托管缓存由服务开发人员明确部署,以降低源服务器负载并有效地交付内容;

强缓存

Cache-Control(通用消息头)通过指定指令来实现缓存机制。缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。

可选值

  • 可缓存性:

    • public:所有方可以缓存;
    • private:只有浏览器可以缓存(私有缓存);
    • no-cache:每次必须先询问服务器资源是否已更新(协商缓存);
    • no-store:不使用任何缓存(放弃私有缓存和代理缓存);
  • 缓存期限

    • max-age:秒(存储周期),用于新鲜度检测;
    • s-maxage:秒(共享缓存如代理等);

常用组合

shell
# 关闭缓存
Cache-Control: no-store

# 使用协商缓存
Cache-Control: no-cache
Cache-Control: max-age=0

# 使用共享缓存,且缓存时间较长,适用于不经常变动的静态资源
Cache-Control:public, max-age=31536000

相关头部

  • 在 HTTP/1.0 中,新鲜度过去由 Expires 标头指定。Expires 响应头使用明确的时间而不是通过指定经过的时间来指定缓存的生命周期;
  • 在 HTTP/1.1 中,Cache-Control 采用了 max-age 用于指定经过的时间。因为时间格式难以解析,也发现了很多实现的错误,有可能通过故意偏移系统时钟来诱发问题;
  • 如果在 Cache-Control 响应头设置了 max-age 或者 s-max-age 指令,那么 Expires 头会被忽略(该头很少用了);

协商缓存

过时的响应不会立即被丢弃。HTTP 有一种机制,可以通过询问源服务器将陈旧的响应转换为新的响应。这称为验证,有时也称为重新验证。如果内容自指定时间以来没有更改,服务器将响应 304 Not Modified。

  • Last-Modified/If-Modified-Since:基于文件修改时间。
    • Last-Modified 响应首部,资源做出修改的日期及时间。
    • If-Modified-Since 请求首部,上一次Last-Modified值。If-Modified-Since只可以用在GETHEAD请求中。
  • ETag/If-None-Match:基于实体内容生成一段 hash 字符串,http1.1 引入,优先级高于 Last-Modified/If-Modified-Since
    • ETag 响应首部,这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web 服务器不需要发送完整的响应。
    • If-None-Match 请求首部,上一次 ETag 值。

有了 Last-Modified 为什么还要 ETag?

  • Last-Modified精确度比ETag要低, Last-Modified基于时间,精度是秒,假如文件在 1 秒之内修改, Last-Modified无法识别;
  • 一些资源内容未变,但最后修改时间改变了,基于时间来说,资源改变了,但基于内容来说,资源未变;
  • 分布式服务器难以同步文件更新时间;

为了解决这些问题,ETag 响应标头被标准化作为替代方案。

在评估如何使用 ETag 和 Last-Modified 时,请考虑以下几点:在缓存重新验证期间,如果 ETag 和 Last-Modified 都存在,则 ETag 优先。因此,如果你只考虑缓存,你可能会认为 Last-Modified 是不必要的。然而,Last-Modified 不仅仅对缓存有用;相反,它是一个标准的 HTTP 标头,内容管理 (CMS) 系统也使用它来显示上次修改时间,由爬虫调整爬取频率,以及用于其他各种目的。所以考虑到整个 HTTP 生态系统,最好同时提供 ETag 和 Last-Modified。

启发式缓存

HTTP 旨在尽可能多地缓存,因此即使没有给出 Cache-Control,如果满足某些条件,响应也会被存储和重用。这称为启发式缓存。 例如:短时间内重复请求。

应用实践

缓存的默认行为(即对于没有 Cache-Control 的响应)不仅仅是“不缓存”,而是根据所谓的“启发式缓存”进行隐式缓存。

  • 缓存破坏:可以使用包含基于版本号或哈希值的更改部分的 URL 来提供 JavaScript 和 CSS,由于缓存根据它们的 URL 来区分资源,因此如果在更新资源时 URL 发生变化,缓存将不会再次被重用。通过这种设计,JavaScript 和 CSS 资源都可以被缓存很长时间。