HTTP 缓存

缓存的优点

减少冗余的数据传输。多个页面中都展示了同一张图片。 缓解网络瓶颈。尤其是移动网络。 降低了 瞬间拥塞(Flash Crowds) 的可能。例如春运抢票,双十一剁手。 降低了距离时延。比如海南三亚的用户访问黑龙江哈尔滨的服务器(3441km),单光速(299792.458km/s)延迟就需要(23 ms)。

缓存的问题

缓存无法保存每份文档,缓存的副本可能与服务器上的不一致。

用已有的缓存副本达到提供服务称为 缓存命中(cache hit),其他一些达到缓存的请求可能由于没有副本可用,而被转发给原始服务器。这称为 缓存未命中(cache miss)。由缓存提供服务的请求所占的比例称为缓存命中率 (cache hit rate)

缓存再验证

由于服务器的内容可能会发生变化。所以需要检测缓存的副本与服务器上的最新版本是否一致,这个行为称之为 HTTP 再验证(revalidation) 或 “新鲜度检测”

缓存对缓存的副本进行再验证时,会向服务器发送一个小的再验证请求验证副本是否变化,如果没变化,服务器会返回一个 304 响应;如果有变化则返回 200 响应, 然后 HTTP Agency 会请求新的副本;如过对象被删除了,服务器返回 404 响应,同时删除缓存。

验证缓存副本为有效时则称之未再 验证命中(revalidate hit)缓慢命中(slow hit)

由缓存提供服务的请求所占比称为 缓存命中率 (cache hit rate)

对于大型文档(高清视频,高保真音频等),由于文档本身的尺寸的问题,虽然访问的次数少,但整个的数据流量的贡献却更大,缓存命中率并不能表现出真实情况,此时,可以考虑采用 字节命中率(byte hit rate) 作为度量值。

缓存的拓扑结构

私有缓存(private cache) 是单个用户专用的(例如,浏览器缓存)。 公有缓存(public cache) 是多个用户共享的,公有缓存是特殊的共享代理服务器,被称为 缓存代理服务器(caching proxy server)代理缓存(proxy cache)

层次化缓存结构

代理缓存的 层次化(hierarchy) 的结构非常有意义,较小的缓存中未命中的请求会被较大的 父缓存(parent cache) 提供服务。

其基本思想就是在靠近客户端的地方使用小型廉价缓存,然后逐步采用更大,更强的缓存来装载更多的用户共享文档。

但是,每一个代理拦截都会增加性能损耗,当链路达到一定长度的时候,这种性能损耗会变得更加明显。

网状缓存结构

网状缓存(cache mesh) 中的代理缓存之间通过某种策略做出动态的缓存通信决策,决定与哪个父缓存进行对话,或彻底绕开缓存,直接链接原始服务器。这种代理缓存会决定选择何种路由对内容进行访问,管理或传送,因此称其为 内容路由器(content router)

一个内容路由器必须包含以下功能:

  • 根据 URL 在父缓存或原始服务器之间进行动态选择

  • 根据 URL 动态选择父缓存

  • 前往父缓存之前,在本地搜索已缓存的副本

  • 允许其它 对等(peer)实体 访问其缓存内容,不允许 Internet 流量访问。

可选的对等支持的缓存称为 兄弟缓存(sibling cache)。HTTP 本身不支持兄弟缓存,所以人们基于 HTTP 进行了扩展,例如 英特网缓存协议 (Iternet Cache Protocol, ICP)超文本缓存协议(HyperText Caching Protocol, HTCP)

缓存(服务器)的处理步骤

  1. 接收 —— 接收请求报文

  2. 解析 —— 解析报文,提取出 URL 和各种首部放入易操作的数据结构中

  3. 查询 —— 检查是否有本地缓存,如果不存在本地缓存,就去获取一份副本

  4. 新鲜度检测 —— 查看副本是否是最新版本

  5. 创建响应 —— 缓存会用新的首部和已缓存的主题构建响应报文

  6. 发送 —— 通过网络响应发送给客户端

  7. 日志 —— 创建一个日志文件描述这个事务

(引用自《HTTP 权威指南》中文版)

HTTP 新鲜度检查策略

过期检查

HTTP 的响应中的首部 ExpiresCache-Control 表示文档的过期时间,如果缓存副本处于这个时间段内,则表示缓存副本未过期。

Expires

Expires 首部是的值是一个绝对时间,例如:

Expires: Fri, 05 Jul 2002, 05:00:00 GMT

Cache-Control

Cache-Control 支持多种指令,例如

Cache-Control: public, max-age=31536000
指令描述请求响应

public

响应可以被任何对象缓存

NO

YES

private

响应只可被单个对象缓存

NO

YES

no-cache

使用缓存前强制向服务器验证

YES

YES

no-store

不允许缓存,并删除当前缓存副本

YES

YES

max-age=<seconds>

设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。

YES

YES

s-maxage=<seconds>

覆盖 max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),私有缓存会忽略它。

NO

YES

max-stale[=<seconds>]

表明客户端愿意接收一个已经过期的资源。

YES

NO

min-fresh=<seconds>

表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应。

YES

NO

stale-while-revalidate=<seconds>

表明客户端愿意接受陈旧的响应,同时在后台异步检查新的响应。

NO

NO

stale-if-error=<seconds>

表示如果新的检查失败,则客户愿意接受陈旧的响应。

NO

NO

must-revalidate

一旦资源过期(比如已经超过 max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求。

NO

YES

proxy-revalidate

must-revalidate 作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。

NO

YES

immutable

表示响应正文不会随时间而改变。资源(如果未过期),因此客户端不应发送重新验证请求头(例如 If-None-MatchIf-Modified-Since)来检查更新,即使用户显式地刷新页面。

NO

NO

no-transform

不得对资源进行转换或转变。

YES

YES

only-if-cached

表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝。

YES

NO

HTTP 1.1 及以上版本中,如果 Cache-Control 的值为 max-age=<seconds>s-maxage=<seconds> 则会忽略 Expires

服务器再验证

如果当前缓存副本不够新鲜,向服务器发起验证。一般通过以下 HTTP 首部上的值进行判断

  • If-None-MatchEtag (组合使用)

  • If-Modified-SinceLast-Modified(组合使用)

If-None-Match / Etag

服务器返回文档内容时,服务器根据一定的策略生成一个 Hash 值,同时在相应头上增加字段 Etag,该字段的值为生成的 Hash 值。 当客服端对该文档进行再验证时,在请求头中增加首部 If-None-Match,首部的值为保存副本的 Etag 的值。 服务器对请求首部中的 If-None-Match 值与文档最新版本的 Hash 值进行比较,如果值相同,则返回 304 相应,如果文档本删除返回 404 相应,如果不相同且没删除则返回 200。 客户端下载新版本的文档,并根据新的响应首部进行文档缓存的处理。

例如 Apache 服务器根据文档的 索引节(INode),大小(Size),和最后修改时间(MTime)进行 Hash 计算得到 Etag 的值

If-Modified-Since/Last-Modified

If-None-MatchEtag 不同的是,If-Modified-SinceLast-Modified 的值是一个具体的时间点

// HTTP Response
Last-Modified:Tue, 24 Feb 2009 08:01:04 GMT

// HTTP Request
If-Modified-Since:Tue, 24 Feb 2009 08:01:04 GMT

试探性过期

如果响应中没有 Cache-Control: max-age<Seconds> 也没有 Expires 首部,缓存可以计算出一个最大试探性周期。

LM-Factor 是一种常见的试探性过期算法。其根据最后修改日期来估计文档有多易变。

缓存通常会为新的文档设置一个默认的新鲜周期,通常是一个小时或一天,有时比较保守的会设置为 0, 有些激进的会设置为一周。

废弃和更新缓存的响应

TL;DR:

  • 在资源“过期”之前,将一直使用本地缓存的响应。

  • 您可以通过在网址中嵌入文件内容指纹,强制客户端更新到新版本的响应。

  • 为获得最佳性能,每个应用都需要定义自己的缓存层次结构。

不过,如果您想更新或废弃缓存的响应,该怎么办?例如,假定您已告诉访问者将某个 CSS 样式表缓存长达 24 小时 (max-age=86400),但设计人员刚刚提交了一个您希望所有用户都能使用的更新。 您该如何通知拥有现在“已过时”的 CSS 缓存副本的所有访问者更新其缓存?在不更改资源网址的情况下,您做不到。(shopee interview question)

缓存检查清单

  • 使用一致的网址:如果您在不同的网址上提供相同的内容,将会多次提取和存储这些内容。 网址区分大小写。特别是 PWA 需要注意该问题 确保服务器提供验证令牌 (ETag):有了验证令牌,当服务器上的资源未发生变化时,就不需要传送相同的字节。

  • 确定中间缓存可以缓存哪些资源:对所有用户的响应完全相同的资源非常适合由 CDN 以及其他中间缓存进行缓存。

  • 为每个资源确定最佳缓存周期:不同的资源可能有不同的更新要求。 为每个资源审核并确定合适的 max-age。

  • 确定最适合您的网站的缓存层次结构:您可以通过为 HTML 文档组合使用包含内容指纹的资源网址和短时间或 no-cache 周期,来控制客户端获取更新的速度。

  • 最大限度减少搅动:某些资源的更新比其他资源频繁。 如果资源的特定部分(例如 JavaScript 函数或 CSS 样式集)会经常更新,可以考虑将其代码作为单独的文件提供。 这样一来,每次提取更新时,其余内容(例如变化不是很频繁的内容库代码)可以从缓存提取,从而最大限度减少下载的内容大小。

参考

HTTP caching http cache

最后更新于