《HTTP权威指南》 第7章 缓存
带着问题学习:
- 缓存如何提高性能
- 如何衡量缓存的有效性
- 缓存置于何处作用最大
- HTTP如何保持缓存副本的新鲜度
- 缓存如何与其他缓存及服务器通信
web缓存是可以自动保存常见文档副本的HTTP设备。
缓存优点
- 减少冗余的数据传输,节省网络费用
- 缓解网络瓶颈问题,更快加载
- 降低了对原始服务器的要求,服务器更快响应,避免过载
- 降低了距离时延
缓解网络瓶颈
破坏瞬时拥塞
很多人同时访问一个文档,造成过多流量峰值,就会出现瞬时拥塞。
降低距离时延
假设Web页面需要请求20个小图片,支持4个并行连接,每个请求传输需要15ms(直线距离除光速传输速度换算而来),一个来回是30ms。全部请求完成至少需要多少时间?
答案如图所示:
基于TCP慢启动的特性,完全启动4个并发连接需要的来回数:连接1 >> 连接2&3+图片1 >> 连接4+图片2&3 =3
剩余请求需要的来回数:(20+1-3)➗4=5
(需要加上一个web基础页面)
因此总来回数是,3+5=8,至少需要8个完整来回,也就是8*30ms=240毫秒
总结一下完全启动所需的连接并发数(N)最少需要几个来回(m):m = N//2 + 1 if N%2 > 0 else N//2
(如果N不能整除2,就是N整除2加1个来回,否则是N整除2个来回)
命中与未命中
用已有的副本为某些到达缓存的请求提供服务,称为缓存命中
。
其他一些到达缓存的请求没有副本可用,而被转发到原始服务端,称为缓存未命中
。
HTTP再验证
验证缓存是否仍是服务器的最新副本,这些”新鲜度检测
“被称为HTTP再验证。
再验证过程:向原始服务器发送一个小的再验证请求,内容无变化时服务端返回304,缓存标记为暂时新鲜,并将副本返回给客户端,这被称为再验证命中
或缓慢命中
。
请求速度快慢:缓存命中 > 缓存再验证命中 > 缓存再验证未命中 ~ 缓存未命中
(成功的再验证比未命中要快,省去了查询数据和构建响应的过程,但失败的再验证几乎与未命中速度一样)
通常进行再验证会添加If-Modified-Since
首部
再验证未命中时,服务器会回送一条带有完整内容的响应,供缓存更新。
若再验证时发现对象被删除,则服务器返回一个404,缓存收到会将其副本删除。
(文档)命中率
由缓存提供服务的请求所占的比例称为缓存命中率
(或文档命中率)。合理命中率约为40%。
字节命中率
由于文档的尺寸不同,文档命中率不能说明一切,更愿意使用字节命中率作为度量值。
字节命中率表示缓存提供的字节在传输的所有字节中所占的比例。100%字节命中率说明全部来自缓存。
区分命中和未命中
客户端可以通过响应的Date
首部与当前时间判断,日期早说明是缓存。或者通过Age
首部来检测缓存的响应,可分辨出响应的使用期。
缓存的拓扑结构
单个用户专有的缓存为私有缓存
,公共的缓存被称为公有缓存
。
私有缓存
Web浏览器有内建的私有缓存,允许用户自行配置。
公有缓存
公有缓存是特殊的共享代理服务器,被称为缓存代理服务器
(代理缓存)。
代理缓存的层次结构
在缓存层次结构深的情况下,请求可能要穿过很长一溜缓存,每个拦截代理都会增加性能损耗。
各类型缓存
有些网格结构会构建复杂的网状缓存
。网状缓存中的代理缓存会进行更复杂的对话,做出动态缓存通信决策,决定与哪个父缓存对话,或者绕开缓存直接连接原始服务器,这种代理缓存可称为内容路由器
。
内容路由器功能:选择父缓存or原始服务器 >> 选择特定父缓存 >> 在前往父缓存前搜索本地副本 >> 允许其他缓存对其缓存的部分内容进行访问
缓存之间允许不同组织互为对等实体,提供可选的对等支持的缓存称为兄弟缓存
,但HTTP并不支持兄弟缓存,所以有额外的协议对HTTP进行了扩展,比如因特网缓存协议ICP和超文本缓存协议HTCP。
缓存的处理步骤
大多数缓存都会保存缓存命中和未命中数据的统计数据,将条目插入一个用来显示请求类型、URL和所发生事件的日志文件。
缓存处理流程图
保持副本的新鲜
文档过期首部
HTTP让原始服务器向每个文档附加一个过期日期Cache-Control
首部和Expires
首部
缓存过期前,可以以任意频率使用这些副本,除非客户端请求阻止提供已缓存或未验证资源的首部,过期后必须与服务器进行核对。
过期日期和使用期
Cache-Control
首部使用的是相对时间而非绝对日期,绝对日期依赖计算机时钟的正确设置,一般更倾向于用相对时间的Cache-Control
首部。
服务器再验证
HTPTP协议要求行为正确的缓存返回下列内容之一:
- 足够新鲜的已缓存副本
- 再验证后仍然新鲜的已缓存副本
- 再验证时发现原始服务器故障,返回一条错误报文
- 附有金高信息说明内容可能不正确的已缓存副本
用条件方法进行再验证
HTTP定义了5个条件请求首部,最有用的是If-Modified-Since
(IMS)和If-None-Match
。
If-Modified-Since:Date再验证
If-Modified-Since
为真表示文档被修改了,服务器返回新首部的新文档和新的过期时间给缓存,没修改过为假,会返回一个新的过期日期。可以与Last-Modified
首部配合工作。
判断的时候是将IMS日期于最后修改日期进行字符串匹配(即判断更新日期是否一致),是“如果最后的修改不是在这个确定的日期进行的”,而不是“如果在这个日期之后没有被修改过”。
If-None-Match:实体标签再验证
遇到下方的场景通常用If-None-Match
实体标签再验证:
- 有些文档会被周期性写入,内容没有变化,但修改日期改变了
- 文档可能被修改了,但该改动不重要
- 有些服务器无法准确判定其页面的最后修改日期
- 有些文档会以亚秒(一秒的十亿分之一)间隙发生变化,以一秒为粒度的修改日期可能不够用
If-None-Match
通过服务器返回的实体标签(ETag
)再验证。
也可以在If-None-Match
首部包含几个实体标签,逗号隔开,表示缓存已存在这些实体标签的对象副本。
If-None-Match: "v2.6"
If-None-Match: "v2.4","v2.5","v2.6"
If-None-Match: "foobar","A34FAC0095","Profiles in Courage"
强弱验证器
实体标签和最近修改日期都是缓存验证器
。HTTP/1.1支持弱验证器,表示进行了少量修改,声明是“足够好”的等价体。服务器用“W/
”标识弱验证器。
ETag: W/"v2.6"
If-None-Match: W/"v2.6"
什么时候使用实体标签和最近修改日期
- 服务器只回送了一个
Last-Modified
最后修改日期,则客户端用If-Modified-Since
验证 - 服务器只回送了
Etag
实体标签,客户端用If-None-Match
验证 - 实体标签和最后修改日期都提供了,则都进行验证
控制缓存的能力
缓存多长时间,按照优先级递减的顺序,服务器可以:
- 附加一个
Cache-Control: no-store
首部到响应中去; - 附加一个
Cache-Control: no-cache
首部到响应中去; - 附加一个
Cache-Control: must-revalidate
首部到响应中去; - 附加一个
Cache-Control: max-age
首部到响应中去; - 附加一个
Expires
日期首部到响应中去; - 不附加过期信息,让缓存确定自己的过期日期。
no-store
首部(禁止复制响应)和no-cache
首部(新鲜度再验证前不能返回缓存)可以防止缓存提供未经证实的已缓存对象。
must-revalidate
表示严格遵循过期时间,到期要进行再验证,验证通过才发送副本。
max-age
最长可以处于新鲜状态的秒数,为0则不缓存,如:Cache-Control: max-age=0
。s-maxage
类似,但仅适用于公有缓存。
不推荐使用Expires
绝对日期作为过期日期
试探性过期
服务器没有提供过期相关的首部,缓存可以使用任意算法(比如LM-Factor)计算出一个试探性的最大使用期,但如果大于24小时,则应该添加一个试探性过期警告13。(默认的新鲜周期通常是一小时或一天)