Web 缓存
什么是 Web 缓存
Web缓存(或HTTP缓存)是用于临时存储(缓存)Web文档(如HTML页面和图像),以减少服务器延迟的一种信息技术。Web缓存系统会保存下通过这套系统的文档的副本;如果满足某些条件,则可以由缓存满足后续请求。[1] Web缓存系统既可以指设备(英语:Server appliance),也可以指计算机程序。
- HTTP 缓存分为两种:
强制缓存、协商缓存
TIP
缓存主要是针对html,css,img等静态资源,常规情况下,我们不会去缓存一些动态资源,因为缓存动态资源的话,数据的实时性就不会不太好,所以我们一般都只会去缓存一些不太容易被改变的静态资源
优缺点
优点:
- 减少冗余的数据传输,节省了客户端流量
- 减少页面打开时间(因为已经缓存了相对应的资源文件)
- 减少服务器带宽压力
缺点:
- 占内存(有些缓存会被存到内存中)
流程图:

控制缓存的基本机制
新鲜度
- 允许在不在源服务器上重新检查的情况下使用一个响应,并且可以由服务器和客户端来控制。例如,Expires响应头给出文档过期的日期,而Cache-Control: max-age指示告诉缓存该响应在多少秒内保持新鲜。
验证
- 可用于检查缓存的响应是否过时之后仍然有效。例如,若响应有一个Last-Modified头,缓存可以使用If-Modified-Since头来发出一个条件请求,来查看它是否已经改变。ETag(实体标签)机制还允许强弱验证。
失效
- 通常是另一个请求通过缓存的一个结果。例如,如果与缓存的响应关联的URL随后获得POST、PUT或DELETE请求,则缓存的响应将失效。
Web 缓存的流程
- 客户端发起请求 –> 无缓存 –> 连接服务器 –> 存缓存 –> 客户端得到数据
- 客户端发起请求 –> 有缓存 –> 够新鲜 –> 使用缓存 –> 客户端得到数据
- 客户端发起请求 –> 有缓存 –> 服务器验证是否过期 –> 没过期 –> 更新缓存的新鲜度 –> 客户端得到数据
- 客户端发起请求 –> 有缓存 –> 服务器验证是否过期 –> 已过期 –> 连接服务器 –> 存缓存 –> 客户端得到数据
强制缓存
Expires 实现强缓存
Expires 作用设定一个强缓存时间。在此时间范围内,则从内存(或磁盘)中读取缓存返回
- 缺陷:过度依赖本地时间,在客户端与服务器时间不一致情况下会存在问题,所以现在基本是用
Cache-control
# 设置方式
Expires: Wed, 21 Oct 2015 07:28:00 GMT
Cache-control 实现强缓存
请求头带上 Cache-control,并且定义相应属性。
Cache-control有max-age、s-maxage、no-cache、no-store、private、public这六个属性max-age:客户端资源缓存时长(单位秒)s-maxage:代理服务器缓存的时长(单位秒)no-cache:强制进行协商缓存no-store:禁止任何缓存策略public:资源可以被浏览器缓存也可以被代理服务器缓存private:资源只能被浏览器缓存
max-age与s-maxage
max-age表示的时间资源在客户端缓存的时长,而s-maxage表示的是资源在代理服务器可以缓存的时长。s-maxage是代理服务端的缓存时长,他必须和public属性一起使用(public属性表示资源可以在代理服务器中缓存)。
Cache-Control:max-age=60
# 60就是需要缓存的秒数。从第一次请求资源的时候开始,往后60秒内,若再次请求资源,则直接从磁盘(或内存中读取),不与服务器做任何交互。
# s-maxage 同理
no-cache与no-store
no-cache 设置了该属性后,会直接跳过强缓存的校验,直接去服务器进行协商缓存
TIP
注:no-cache 和 no-store 是一组互斥属性,这两个属性不能同时出现在 Cache-Control 中
public与private
这两个属性是用于,资源是否可以在代理服务器进行缓存的属性。
public表示资源在客户端和代理服务器都可以被缓存private表示资源只能在客户端被缓存,拒绝资源在代理服务器缓存- 如果这两个属性值都没有被设置,则默认为
private
TIP
注:public 和 private 是一组互斥属性,这两个属性不能同时出现在 Cache-Control 中
- 注:
Cache-control设置多值,用逗号分隔
Cache-control:max-age=60,s-maxage=60,public
协商缓存
基于 last-modified 的协商缓存
- 首先需要在服务器端读出文件修改时间
- 将读出来的修改时间赋给响应头的
last-modified字段 - 最后设置
Cache-control:no-cache
- 缺陷:依赖于文件更新时间,比如在请求过程中进行了文件更新,存在时间差问题

基础 ETag 的协商缓存
ETag 比较文件指纹(根据文件内容计算出的唯一哈希值,文件内容一旦改变则指纹改变)
流程:
- 第一次请求某资源的时候,服务端读取文件并计算出文件指纹,将文件指纹放在响应头的
ETag字段中跟资源一起返回给客户端。 - 第二次请求某资源的时候,客户端自动从缓存中读取出上一次服务端返回的
ETag也就是文件指纹。并赋给请求头的if-None-Match字段,让上一次的文件指纹跟随请求一起回到服务端。 - 服务端拿到请求头中的
is-None-Match字段值(也就是上一次的文件指纹),并再次读取目标资源并生成文件指纹,两个指纹做对比。如果两个文件指纹完全吻合,说明文件没有被改变,则直接返回304状态码和一个空的响应体并return。如果两个文件指纹不吻合,则说明文件被更改,那么将新的文件指纹重新存储到响应头的ETag中并返回给客户端

缺点:
ETag需要计算文件指纹这样意味着,服务端需要更多的计算开销。如果文件尺寸大,数量多,并且计算频繁,那么ETag的计算就会影响服务器的性能。显然,ETag在这样的场景下就不是很适合。ETag有强验证和弱验证,所谓将强验证,ETag生成的哈希码深入到每个字节。哪怕文件中只有一个字节改变了,也会生成不同的哈希值,它可以保证文件内容绝对的不变。但是,强验证非常消耗计算量。ETag还有一个弱验证,弱验证是提取文件的部分属性来生成哈希值。因为不必精确到每个字节,所以他的整体速度会比强验证快,但是准确率不高。会降低协商缓存的有效性。
TIP
值得注意的一点是,不同于cache-control是expires的完全替代方案(说人话:能用cache-control就不要用expiress)。ETag并不是last-modified的完全替代方案。而是last-modified的补充方案(说人话:项目中到底是用ETag还是last-modified完全取决于业务场景,这两个没有谁更好谁更坏)。
如何设置缓存
后端在服务器配置设置或代码内设置
不同文件对应哪些缓存
有哈希值的文件设置强缓存,没有哈希值的文件设置协商缓存(比如index.html)
为什么感觉哈希值去设置?
因为哈希值会在每次打包后进行变更,如 js、css 文件。更改了文件名的文件,就是一个新的文件。
总结
http缓存可以减少宽带流量,加快响应速度。
关于强缓存,
cache-control是Expires的完全替代方案,在可以使用cache-control的情况下不要使用expires关于协商缓存,
etag并不是last-modified的完全替代方案,而是补充方案,具体用哪一个,取决于业务场景。有些缓存是从磁盘读取,有些缓存是从内存读取,有什么区别?答:从内存读取的缓存更快。
所有带304的资源都是协商缓存,所有标注(从内存中读取/从磁盘中读取)的资源都是强缓存。
参考:前端缓存