跳到主要内容

缓存

2023年12月23日
柏拉文
越努力,越幸运

一、认识


在发起请求之前,浏览器首先检测请求资源是否在缓存中并且新鲜。如果资源未缓存, 发起新请求; 如果已缓存, 检验是否足够新鲜, 足够新鲜直接提供给客户端, 否则与服务器进行验证。HTTP 缓存工作流为: 浏览器第一次向服务器发起该请求后,服务器返回200,客户端拿到请求结果后,将请求结果和缓存标识存入浏览器缓存。这个缓存标识包含Cache-ControlExpiresDate (其中Date要与Cache-ControlExpires对比)。浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。当再次浏览器向服务器发送请求时:

一、首先检测是否命中强缓存: 强缓存(Strong Cache, 浏览器在规定的有效期内, 说明缓存足够新鲜, 则直接使用本地缓存, 不发起网络请求。强缓存 主要依赖 HTTP 头信息中的 ExpiresCache-Control 与 缓存的请求标识的Date时间 进行比较。

  • Cache-Control, 如果缓存标识中有 Cache-Control 响应头字段, 优先根据Cache-Control的属性值来检测是否命中强缓存。Cache-ControlHTTP/1.1 推荐使用的缓存头,可以指定多种缓存策略:

    • Cache-Control: max-age=0: 直接进入协商缓存阶段

    • Cache-Control: max-age=3600: 表示资源在 3600 秒内有效。意味着从上一次缓存的请求标识的Date时间后的3600秒内再次请求资源命中强缓存。

    • Cache-Control: no-cache: 要求客户端在使用缓存前必须向服务器发送验证请求, 直接进入协商缓存阶段。

    • Cache-Control: no-store: 完全不使用缓存,每次都重新请求。

    • Cache-Control: public/private: 指定资源是否允许被共享缓存存储。

  • Expires, 如果缓存标识中没有Cache-Control响应头字段, 会根据 Expires 来检测是否命中强缓存。Expires HTTP/1.0 提供的缓存头, 指定资源过期的具体时间。通过对比客户端请求时间与 缓存标识 Expires 过期时间进行对比, 如果当前客户端时间在Expires过期时间之前命中强缓存,如果在Expires过期时间之后会进入协商缓存。

    Expires: Wed, 21 Oct 2025 07:28:00 GMT

    Expires 使用的是绝对时间, 不够灵活, 容易因客户端和服务器时间不同步而出问题。我们可以修改客户端的时间来改变强缓存的命中结果。如果我们手动修改当前客户端时间为Expires过期时间之后,那么会进入协商缓存。

二、如果未命中强缓存,检测是否命中协商缓存: 当强缓存失效后(有缓存, 但是不新鲜),浏览器会向服务器发送条件请求,询问资源是否发生变化。如果资源未变化,服务器返回 304Not Modified),浏览器继续使用本地缓存。

  • ETag / If-None-Match, 如果上一次请求的缓存标识中有Etag响应头字段, 优先验证 ETag / If-None-MatchEtag 表示服务器为资源生成的唯一标识符(通常基于内容的哈希),资源更新时 ETag 会改变。If-None-Match 表示浏览器在请求中带上上次缓存时的 ETag, 服务器比较后,若一致则返回 304流程为: 将上一次请求的缓存标识中有Etag响应头字段赋值给 If-None-Match 请求头, 发送给服务器。如果服务器生成的新 ETag 与 接收到的 If-None-Match 匹配, 则返回 304, 表示资源未更新, 返回状态码304和空响应体给浏览器, 浏览器继续使用缓存资源。如果不匹配, 说明资源有更新, 就返回状态码 200 和新的资源。Etag(响应头)/If-None-Match(请求头) 的协商缓存策略精确度非常高, 但是因为 Etag 生成过程中需要服务器付出额外开销,会影响服务器端的性能。

  • Last-Modified / If-Modified-Since, 如果缓存标识中没有Etag响应头字段会根据Last-Modified响应头字段来验证是否命中协商缓存。Last-Modified 表示服务器告知资源的最后修改时间。If-Modified-Since 表示浏览器在请求中带上上次缓存时的最后修改时间,服务器比较后,如果资源没有变化,则返回 304流程为: 将上一次请求的缓存标识 Last-Modified 响应头字段赋值给 If-Modified-Since 请求头, 发送给服务器。服务器再把这两个字段的时间对比,如果是一样的,就说明文件没有被更新过,就返回状态码304和空响应体给浏览器,浏览器直接拿过期了的资源继续使用即可;如果对比不一样说明资源有更新,就返回状态码200和新的资源。Last-Modified(响应头)/If-Modified-Since(请求头)的这个协商策略有缺点,如果本地打开了缓存文件,即使没有对文件进行修改,但还是会造成Last-Modified被修改,服务器端不能命中缓存导致发送相同资源。其次因为Last-Modified只能以秒计时,如果在不可感知的时间内修改了文件,服务器端会认为还是命中了,无法返回正确的资源,还有如果资源有周期性变化,如资源修改后,在一个周期内又改回了原来的样子,我们认为这个周期前的缓存是可以使用的,但是Last-Modified不这样认为。所以,浏览器优先采用Etag(响应头)/If-None-Match(请求头)协商缓存策略。

二、问题


2.1 HTTP 缓存原理?

如上。

2.2 HTTP 缓存相关请求头和响应头?

强缓存相关请求头和响应头

  • Age: 有关缓存的请求头。出现此字段, 表示命中代理服务器的缓存. 它指的是代理服务器对于请求资源的已缓存时间, 单位为秒

  • Date: 指的是响应生成的时间. 请求经过代理服务器时, 返回的Date未必是最新的, 通常这个时候, 代理服务器将增加一个Age字段告知该资源已缓存了多久.

  • Cache-Control, 如果缓存标识中有 Cache-Control 响应头字段, 优先根据Cache-Control的属性值来检测是否命中强缓存。Cache-ControlHTTP/1.1 推荐使用的缓存头,可以指定多种缓存策略

  • Expires, 如果缓存标识中没有Cache-Control响应头字段, 会根据 Expires 来检测是否命中强缓存。Expires HTTP/1.0 提供的缓存头, 指定资源过期的具体时间。

协商缓存相关请求头和响应头

  • ETag / If-None-Match, 如果上一次请求的缓存标识中有Etag响应头字段, 优先验证 ETag / If-None-MatchEtag 表示服务器为资源生成的唯一标识符(通常基于内容的哈希),资源更新时 ETag 会改变。If-None-Match 表示浏览器在请求中带上上次缓存时的 ETag, 服务器比较后,若一致则返回 304

  • Last-Modified / If-Modified-Since, 如果缓存标识中没有Etag响应头字段会根据Last-Modified响应头字段来验证是否命中协商缓存。Last-Modified 表示服务器告知资源的最后修改时间。If-Modified-Since 表示浏览器在请求中带上上次缓存时的最后修改时间,服务器比较后,如果资源没有变化,则返回 304