声明:本人原创文章,详细内容已发布在我的微信个人技术公众号---网络技术修炼,公众号总结普及网络基础知识,包括基础原理、网络方案、开发经验和问题定位案例等,欢迎关注。
缓存概念
缓存处理请求步骤
缓存如果查询到某个请求已经有缓存,那么需要进一步检查该资源的新鲜度,根据新鲜度和请求中的字段综合评估是否要去服务端拉取新鲜的资源。
注意:
- 创建响应时候要注意版本匹配,如果服务器响应和客户端请求的http版本不一致,要在缓存服务器做转换。
- 缓存有通用的日志规范,常见的日志为Squid日志格式和网景的可扩展通用日志。
- 缓存中过期的资源也不一定要删除,因为过期了也能用,删除一般采用特定算法,如LRU。
以http get为例缓存处理逻辑如下图所示。
服务端响应写入缓存
相关http字段
- 缓存服务器如果不支持Range和Content-Range,那么不要缓存不完整的response。
- 缓存服务器在未接收完一个Content-Range的content时候,不要给客户端该部分的应答。
- 缓存服务器可以将多个Content-Range的content片段组合到一起应答给客户端。
缓存响应的创建
Vary
Vary 是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该使用一个缓存作为响应还是向源服务器请求一个新的响应。当响应中有vary字段的时候,缓存时候必须将vary字段一并缓存,下次请求的时候除了url外,还需要与vary缓存的字段完全一致才可以返回缓存内容。
例如,如果响应的 Vary 字段设置为 "Accept-Language",那么在缓存响应时,代理服务器和客户端应该按照请求中的 Accept-Language 字段来区分不同的响应。这意味着,如果一个客户端发出了两个不同的请求,其中一个请求的 Accept-Language 字段是 "en-US",另一个请求的 Accept-Language 字段是 "fr-FR",那么这两个请求应该获得不同的响应。
如果 Vary 字段为空,那么代理服务器和客户端可以缓存响应,并在任何请求中重用它。
内容协商除了vary,主要靠Accept来实现,Accept 字段,详见下表:
新鲜度
相关http字段
判断一个缓冲是否过期可以使用以下公式:
response_is_fresh=freshness_lifetime > current_age
其中freshness_lifetime为新鲜度声明周期,current_age为缓存已经生存的时间,这两个值都是使用的相对时间,后面有详细的计算方法。
注意:这个计算只能说明缓存中的内容是否新鲜,具体能否直接将该内容回复给客户端,还需要结合请求头中cache-control的max-age、max-stale、min-fresh字段进行综合判定。这个也比较好理解比如客户购买了一个苹果,吃的时候会看一下是否在保质期内,刚刚就是计算是否在保质期内,但不同人的处理可能不同,有的人发现过期两天也会吃,有的人发现临近过期就不吃了,请求中cache-control的max-age、max-stale、min-fresh字段就代表了不同处理方式的人。综上,关于缓存服务器对于一个存在的缓存资源是否能直接返回给客户端应该先计算freshness_lifetime - current_age,然后再综合请求中cache-control判断。
freshness_lifetime的计算
新鲜度生命时间(freshness_lifetime)表示资源从诞生到过期的相对时间(以秒为单位),其计算按照如下优先级依次计算,如果某个优先级的字段符合计算条件则跳过后面的计算:
- 共享式缓存优先使用应答中的s-maxage。
- 使用应答中的max-age。
- 应答中的Expires减去Date。
- 启发式缓存估算的时间(计算方式详见:https://httpwg.org/specs/rfc7234.html#heuristic.freshness)。
推荐使用Date和Last-Modified 计算的缓存间隔时间除以10,即:
if ((last_modified > 0) && (date > 0) && (date - last_modified) > 0) { return (date - last_modified) / 10; }
current_age的计算
current_age表示资源从诞生到现在的相对时间( 以秒为单位),其参与current_age的计算的因素有:
- Age字段表示实体从产生到现在以秒为单位经过多长时间了(多级缓存的场景,上一级缓存会填充该字段)。
- Date字段表示报文内容在源服务器中诞生的时间。
- 主机时间。
计算方法:
方法1: 用收到响应的时间减去Date字段的值。
用response_time表示收到响应的时间,用date_value表示Date字段的值,那么
current_age = response_time - date_value
但是,接收端和源服务器间很可能会有clock skew(时钟偏差),为了防止这种情况,将负数结果赋值为0,所以该计算方案最终为:
current_age = max(0, response_time - date_value)
方法2:逐跳计算。
接收端收到响应报文时的Age值等于上一跳节点中缓存的Age值加上传输时延。用previous_hop_age_value表示上一跳节点中缓存对的Age值,用response_delay表示传输时延,那么计算公式如下:
current_age = previous_hop_age_value + response_delay
respose_delay可以粗略地计算为得到响应时间减去发出请求的时间,这里你可能会问,为什么不要再除以二呢,因为HTTP对Age的计算策略是宁可多算也不肯少算的,多算顶多缓存新鲜时间变短,产生额外的新鲜度验证,但是少算的话,即使过期了,客户端还会把它当成新鲜的用。
response_delay = response_time - request_time
这种方法的好处是response_time和request_time都是本地的时间,不存在时间偏差。
综上所述,通常响应报文的计算会综合上述两种方法,取最大的一个。
#第一种计算方式 age_value_by_date = max(0, response_time - date_value) #第二种计算方式 response_delay = response_time - request_time age_value_by_hop = previous_hop_age_value + response_delay #两种取大的 current_age = max(age_value_by_date, age_value_by_hop)
校验
相关http头
- 条件验证通过服务器回复304 Not Modified,再验证实效返回200 ok。
- 条件验证标签如果同时存在为“与”的逻辑关系,都满足才能返回304 Not Modified。
缓存控制
Cache-Control为http1.1上定义的缓存控制策略。
TODO:请求中优先级是什么,比如no-cache和only-if-cached如果同时存在,需要去服务端条件get吗?
其他
Pragma
Pragma: no-cache 含义与Cache-Control:no-cache相同,是为了兼容http 1.0版本的字段,当报文头同时出现Cache-Control和Pragma时候,Pragma被忽略。
Cache Control Extensions
详见:https://httpwg.org/specs/rfc7234.html#cache.control.extensions
Warning
此信息通常用于警告应用于消息有效负载的缓存操作或转换可能引入的错误。详细解释见:https://httpwg.org/specs/rfc7234.html#header.warning
Age
current_age的计算章节已经提到Age字段表示实体从产生到现在以秒为单位经过多长时间了源站服务器不会添加字段,但是缓存服务器会添加,也为多级缓存计算current_age提供了一个重要参数。详见:https://httpwg.org/specs/rfc7234.html#header.age
Via
Via 是一个通用首部,是由代理服务器添加的,适用于正向和反向代理,在请求和响应首部中均可出现。
参考文档
Hypertext Transfer Protocol (HTTP/1.1): Caching https://httpwg.org/specs/rfc7234.html
Hypertext Transfer Protocol -- HTTP/1.1 https://www.rfc-editor.org/rfc/rfc2616#section-14.44
Hypertext Transfer Protocol (HTTP/1.1): Range Requests https://www.rfc-editor.org/rfc/rfc7233
《http权威指南》
HTTP中缓存的使用期计算(Age Calculation)https://blog.csdn.net/sxh951026/article/details/77934463