chenqunfeng / Blog

个人技术记录博客
6 stars 1 forks source link

HTTP缓存 #13

Open chenqunfeng opened 6 years ago

chenqunfeng commented 6 years ago

前言

重用已获取的资源能够有效的提升网站与应用的性能。Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间。借助 HTTP 缓存,Web 站点变得更具有响应性。

用户操作与缓存

根据用户操作和资源的缓存情况,我们可以区分以下几种情况:

缓存相关首部

Pragma

HTTP/1.0中规定的通用首部,语法为Pragma: no-cache,作用效果与Cache-Control: no-cache相同,表示缓存链禁止返回一个未经源站验证的缓存响应,也就是说,会向源站确认资源是否可以使用缓存。而这个一般会出现在我们强制刷新的请求报文中。

1522292851

Expires

表示资源到期时间,设置的时间通常都是服务器时间。在优先级方面,比Cache-Control低,也就是两者同时出现的话,Expires的设置会被覆盖。如果浏览器没有设置ExpiresCache-Control,那么浏览器默认会采用一个启发式的算法,通常会取相应头的Date和Last-Modified差值的10%作为缓存时间,即Date + (Date - Last-Modified) / 10

Cache-Control

HTTP/1.1中规定的通用首部,控制资源的缓存的存储和过期,覆盖与之冲突的其他规则,如Expires,语法为Cache-Control:cache-directive。关于用户操作和资源的缓存情况,Cache-Control的表现与Expires表现一致。 cache-directive共有12种,分别存在请求指令和响应指令中:

请求指令:

Cache-Control: max-age=<seconds>
Cache-Control: max-stale[=<seconds>]
Cache-Control: min-fresh=<seconds>
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: only-if-cache

响应指令:

Cache-Control: must-revalidate
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: public
Cache-Control: private
Cache-Control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-Control: s-maxage=<seconds>

max-age

当在请求指令中时,告知服务器表示客户端希望接收一个存在时间不大于seconds秒的资源,但是配合max-stale使用则可以突破这个规则,否则客户端是不会接受过期的响应的。

当在响应指令中时,告知客户端该资源在seconds秒内都是新鲜的,无需向服务器发请求。

max-stale

告知服务器表示客户端能容忍资源的最大过期时间,若定义了seconds,则为seconds秒,若没有定义seconds则为任意超出时间。

min-fresh

告知服务器表示客户端能容忍的最小过期时间,即表示资源需要在seconds秒内未过期才会响应。

no-cache

当在请求指令中时,告知服务器不直接使用缓存,会向服务器发送请求确认是否使用缓存。

当在响应指令中时,告知客户端不直接使用缓存,要向服务器发送请求确认是否使用缓存。

no-store

不管是在请求指令还是响应指令中,都表示所有内容不会保存到缓存或Internet临时文件中。

no-transform

当在请求指令中时,告知服务器表示客户端希望获取的实体数据没有被转换过,比如压缩。

当在响应指令中时,告知客户端缓存文件时不得对实际数据做任何改变。

only-if-cache

不管是在请求指令还是响应指令中,都表示客户端希望获取缓存内容,只要缓存内容,而不用向原服务器发送请求。也就是说要么返回缓存,要么返回504响应。

must-revalidate

告知客户端,如果内容失效(指超出max-age的时间,可以无视max-stale),强制重新向原服务器发起验证,如果无法到达原服务器,则必须返回一个504响应。

public

表示资源可以被客户端和代理服务器缓存。

private

表示资源仅可以被客户端缓存,而代理服务器不能缓存。

proxy-revalidate

与must-revalidate类似,但仅能应用于共享缓存,如代理。

s-maxage

与max-age类似,但仅能应用与共享缓存,如代理。

关于max-age、max-stale、min-fresh三者的关系,我们这里假定一个场景来区分三者的关系。 假设某个资源在4月1号缓存,且会在4月8号过期,然后三个首部的设置如下 max-age=10 24 3600;// 覆盖原缓存周期,即1 + 10,即11号失效 max-stale=2 24 3600; // 缓存过期后2天内还有效,即8 + 2,即10号失效 min-fresh=3 24 3600; // 至少保证3天的有效期,即8 - 3,即5号失效 客户端总是会采取最保守的缓存策略,所以,4月5号后,针对该资源会重新向服务器发起验证。

Last-Modified

用于标识资源的最后一次修改时间,格式为GMT,时间精确到秒,因此不太适合短时间内频繁改动的资源。不仅如此,可能在编译阶段会出现资源内容没有变化但是修改时间缺改变的情况。在优先级方面,比ETag的优先级低。

If-Modified-Since

缓存校验字段,告知服务器如果资源的最后的修改时间与服务器上一致,则直接返回304,否则需要重新下载资源。

If-Unmodified-Since

缓存校验字段,告知服务器如果资源未修改则更新,否则返回412。适用场景如断点续传文件,传输文件时需要确保文件未更改。

ETag

实体标签,服务器资源的唯一标识。如果给定URL中的资源更改,则一定要生成新的Etag值。一般是使用inode+mtime进行计算。

语法形式为ETag: "xxx",要求只要双引号内有值即可,而不关心具体是什么。

而根据双引号内的生成规则,可以区分为弱验证器和强验证器,弱ETag: W/ "xxx",强ETag: "xxx"。

强校验的ETag匹配要求两个资源内容的每个字节需完全相同,包括所有其他实体字段(如Content-Language)不发生变化。强ETag允许重新装配和缓存部分响应,以及字节范围请求。 弱校验的ETag匹配要求两个资源在语义上相等,这意味着在实际情况下它们可以互换,而且缓存副本也可以使用。不过这些资源不需要每个字节相同,因此弱ETag不适合字节范围请求。当Web服务器无法生成强ETag不切实际的时候,比如动态生成的内容,弱ETag就可能发挥作用了。

强弱ETag类型的出现与Apache服务器计算ETag的方式有关。Apache默认通过FileEtag中FileEtag INode Mtime Size的配置自动生成ETag(当然也可以通过用户自定义的方式)。假设服务端的资源频繁被修改(如1秒内修改了N次),此时如果有用户将Apache的配置改为MTime,由于MTime只能精确到秒,那么就可以避免强ETag在1秒内的ETag总是不同而频繁刷新Cache(如果资源在秒级经常被修改,也可以通过Last-Modified来解决)。

PS:一般是使用inode+mtime进行计算,而不同的物理机上inode是不同的,这导致了在分布式的Web系统中,不同的物理机响应的ETag不一致而使缓存失效,而令304降级为200。解决办法也有从ETag算法中剥离inode,只是使用mtime,但是这样实际上和Last-Modified就一样了。当然也可以使ETag对静态资源的算法也是通过hash计算的。不过由于一般我们会使用CDN技术,因此集群部署中的ETag问题并不会造成太大的影响。

If-None-Match

缓存校验字段,跟ETag搭配,常用于判断缓存资源是否有效,优先级比If-Modified-Since高。语法为 If-None-Match: ETag_value 或者If-None-Match: ETag_value, ETag_value, …

If-Match

缓存校验字段,其值为上次收到的一个或多个etag 值。语法为If-Match: ETag_value 或者 If-Match: ETag_value, ETag_value, …

Age

若响应报文中出现该首部,表示命中代理服务器缓存,对应的时间指代理服务器对于请求资源的已缓存时间。

Age: 2383321 表示该资源目前缓存了2383321秒

Date

指响应生成的时间。请求经过代理服务器时,返回的Date未必是最新的,这种情况下,代理服务器会通过Age首部告知资源缓存了多久。

Vary

主要意义是告诉代理服务器/CDN,如何判断请求是否一样。语法为Vary: ( * | header-name )。表明在内容协商算法中选择一个资源的时候应该使用哪些头部信息。比如使用了User-Agent,就算资源没变服务器也会根据不同的user-agent重新响应资源,在大多数情况下可能会导致服务器针对不同浏览器缓存了多份相同的资源;除非有定制化的需求,例如桌面端和移动端返回的资源必须不一致才需要开启。

其中Accept分别是以下几种:

例如 客户端发送的请求头如下:

Accept: text/css,*/*;q=0.1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

表示它可以接受MIME类型为text/css或其他任意类型,其中text/css权重为1,其他任意类型权重为0.1;支持采用gzip和deflate压缩过的资源;可以接受zh-CN和zh两种语言,其中zh-CN权重为1,zh权重为0.9。 服务器发送的响应头如下:

Content-Encoding: gzip
Content-Type: text/css

表示响应了MIME类型为text/css的文档,同时对文档进行了gzip压缩。

缓存策略

Last-Modified/If-Modified-Since/Expires

Expires会根据用户的操作做出不同的响应,在不强制刷新的情况下,如果Expires设定的时间还未过期,则会直接使用缓存中的资源,否则会向服务器发送请求确认。

Last-Modified和If-Modified-Since一般成对出现,客户端发起请求时,会在请求报文中带上If-Modified-Since头部,服务器收到之后会确认资源的最新修改时间,如果一致,则直接在响应中返回这个最新修改时间,告知客户端可直接使用缓存,并返回304;如果比最新的修改时间要早,则会返回新资源,并在响应中返回新的Last-Modified头部,并返回200;如果比最新修改的时间要晚,服务器会认为这是个不合法的请求,会被抛弃。

ETag/If-None-Match/Cache-Control

Cache-Control在使用上与Expires基本一致,只是当两个同时出现的时候,Cache-Control的设置会覆盖Expires的设置。

ETag和If-None-Match一般成对出现,优先级会比Last-Modified和If-Modified-Since要高,也就是说当两个同时出现的时候,会优先验证ETag与If-None-Match是否匹配,相同则告知客户端使用缓存,并返回304,不同则返回新的ETag并让客户端重新下载资源,返回200;在没有ETag 的情况下,一般为不支持http1.1的浏览器,则会使用Last-Modified和If-Modified-Since进行校验。