《HTTP权威指南》笔记:第七章-缓存
一、命中和未命中缓存
使用缓存的副本为到达缓存的请求提供服务被称为缓存命中(cache hit),如果没有已缓存的副本提供,而将请求转发给源服务器,被称之为缓存未命中(cache miss)。
1.1 再验证
WEB
缓存对已缓存的副本进行“新鲜度检测”,会向源服务器发送一个小的再验证请求,如果缓存仍然“新鲜有效”,那么服务器会返回一个304 not modified
,然后缓存将副本提供给客户端。这被称为再验证命中或者缓慢命中。
缓存一般会使用:(1)if-modify-since
;(2)if-match
;等HTTP
首部进行再验证,会有以下结果:
- 再验证命中,服务端响应
304
。 - 再验证未命中,服务端响应
200
并且带有完整内容。 - 对象被删除,服务端响应
404
,缓存清除该资源副本。
1.2 缓存命中率
由缓存提供服务所占的比例被称为缓存命中率,这个数值在0 ~ 1之间,对于现代中等规模以上的WEB
应用来说,命中率保持在40%以上就达标了。
1.2 字节命中率
字节命中率直接体现对流量的节省,也能显著的降低时延,但是两者都是评价缓存性能的重要指标。
1.3 区分命中与未命中
检查HTTP
报文的Date
首部,如果时间早于请求时间,那么就是来自缓存,否则是来自服务端。
二、缓存的拓扑结构
缓存可以是单用户的(私有缓存),也可以是多用户共享的(公共缓存),下面是常见的缓存结构:
client -> private cache -> server
client -> public cache -> server
client -> private cache -> public cache -> server
2.1 私有缓存
Web
浏览器中有内建的私有缓存。
2.2 公共代理缓存
公共缓存时特殊的共享代理服务器,被称为缓存代理服务器。
2.3 代理缓存的层次结构
在代理缓存层次结构中,在靠近客户端的一级缓存中未命中的请求会被导向更高的父缓存,具体结构如下:
client/(private cache) -> first cache -> parent cache -> server
2.4 网状缓存、内容路由以及对等缓存
有些网络结构会哦固件负载的网状缓存(cache mesh),与缓存之间会以更加复杂的方式对话,可以做出动态缓存通信决策,决定与哪一个父缓存通信,或者直接与源服务器通信,也可将这种结构称为内容路由(content router),它具有以下功能:
- 根据
URL
在parent cache
和server
之间做动态选择。 - 根绝
URL
动态选择parent cache
。 - 允许该结构其他缓存对缓存的副本进行访问,但是不允许外网流量访问。
三、缓存处理步骤
对一条HTTP GET
报文的基本缓存处理步骤为:
- 接收。
- 解析,提取报文的
URL
以及headers
。 - 查询,在本地查询是否有已保存副本。
- 新鲜度检测。
- 创建报文,根据检测结果创建响应报文。
- 日志,可选的记录日志。
四、保持副本新鲜
4.1 过期日期和使用期
服务器使用HTTP/1.0+
的expires
或者HTTP/1.1
的cache-control:max-age=value
来规定缓存的使用时间区间。expires
是绝对时间,依赖于客户端的始终,而cache-control
使用的是相对时间,所以现在更倾向使用后者。
4.2 服务器再验证
如果已缓存的内容到期了(强缓存),那么会重新请求服务器对资源进行验证,如果未修改,返回304
,更新缓存副本的首部;如果已修改,则返回200
,然后丢弃原来的副本,缓存新的副本。
4.3 条件再验证
如果采取了协商缓存策略,那么可以发起条件GET
,只有当条件满足了,服务端才会返回对象。HTTP
定义了5个条件首部:
-
if-modified-since:<date>
,如果从指定日期之后文档修改了,则从服务端返回新的对象(响应报文配合last-modified
使用)。 -
if-none-match:<tags>
,如果tag
与服务端指定资源的标记不同,则条件为true
,返回新对象(响应报文配合etag:xxx
使用)。 -
if-unmodified-since:<date>
,在进行部分文件传输时,获取其他部分之前要确保文件未发生改变。 -
if-range
,针对不完整文档的缓存。 -
if-match
,与服务器通信时的并发控制。
4.3.1 if-none-match的必要性
- 有些文档会被周期性重写,尽管内容没改变,但是时间改变了。
- 有些文档的修改不重要,也许只是修改了注释之类的,则不需要重新获取。
- 有些服务器无法准确的判定最后修改日期。
-
if-modified-since
的时间粒度是秒,而有些文档可能在1秒内发生数次变化。
4.3.2 使用if-none-match和if-modified-since的时机
HTTP/1.0
可能不能理解实体标签,所以服务端返回响应的时候最好同时返回last-modified
和etag
这样可以使HTTP/1.0
也能正常响应,如果服务端同时返回这两个首部,那么只有当这两个条件都满足,服务端才能返回304
。
五、如何控制缓存
按照优先级递减的顺序,服务端可以设置以下首部到响应报文中:
cache-control:no-store
cache-control:no-cache
cache-control:must-revalidate
cache-control:max-age
expires
- 不设置相关头部,让缓存自己决定过期时间,可能是首部的
date
减去last-modified
的五分之一(知乎上有人称之为启发式缓存)
5.1 缓存首部
-
cache-control: no-store
,会阻止缓存对响应进行复制即不缓存。 -
cache-control:no-cache
,可以被私有缓存复制保存在本地,但是在提供给客户端使用必须会与服务端进行再验证。 -
cache-control: max-age=100
,缓存的活跃时间。 -
cache-control:s-maxage=100
,针对公共缓存设置的缓存活跃时间。 -
expires:Fri,05 Jul 2020,05:00:00 GMT
,绝对活跃时间,即在指定时间之前是活跃的。 -
cache-control: must-revalidate
,如果不添加这个首部,有的缓存为了提高性能会给客户端提供过期的副本,添加这个首部告诉缓存在副本过期后必须重新请求,如果服务端不可用就必须返回504 gateway timeout
。
5.2 试探性过期
如果响应首部没有设置副本的过期时间,那么缓存会采取一定的算法计算出一个试探性的生存周期,比较常见的是LMF-Factor
算法,具体计算公式为:lm_facotr * max(0, date - lastModified)
,一般lm_factor
会取0.2
。有的缓存保守起见会直接默认副本活跃时间为0,有的缓存则会设置最大值为1天或者1周。
5.3 客户端的新鲜度限制
当我们点击浏览器的刷新按钮重载页面时,一般来说浏览器会强制刷新缓存,客户端可以在请求首部加入cache-control
以加强或者放松对过期时间的限制:
-
max-stale[=<s>]
,如果只设置了max-stale
表明客户端可以接受过期的缓存,如果设置了max-stale=100
,就说明该缓存的副本在100s
内不会过期,相当于是放松了对缓存过期时间的管控。 -
min-fresh=<s>
,要求在指定时间内是有效的。 -
max-age=<s>
,生命周期小于设置的值的副本就会被认为过期,相当于缩短了服务端设置的max-age
。 -
no-cache
,强制缓存进行再验证。 -
no-store
,强制缓存删除相关文档。 -
only-if-cached
,只有当缓存中有该请求的副本,客户端才能获取到该对象(意思是必须从缓存取而不是服务器?)
参考文献
[1]《HTTP权威指南》