重新学习HTTP缓存
HTTP缓存是什么
缓存是计算机科学中非常常见的概念,在HTTP协议中,缓存指:
当发起一个资源的请求时,如果本地存储设备有资源“已缓存”的副本,就直接从本地提取这个文档;如果本地存储设备没有该资源,再去请求原始服务器,并将请求到的结果保存下来等待下次缓存使用。
那么,缓存是基于这样一个假设:在一段时间内,某些资源是不会改变的。
缓存的优点大致有:
- 缓存减少了冗余的数据传输,节省了你的网络费用。
- 缓存缓解了网络瓶颈的问题。不需要更多的带宽就能够更快地加载页面。
- 缓存降低了对原始服务器的要求。服务器可以更快地响应,避免过载的出现。
- 缓存降低了距离时延,因为从较远的地方加载页面会更慢一些。
缓存什么资源
假设我们不知道现在的HTTP缓存策略,就从头开始考虑怎么缓存资源。
首先考虑什么类型的资源可以被缓存?在HTTP方法的语义上来看,应该是幂等的,无副作用的方法请求的资源可以被缓存。很好理解,缓存就是假设一定时间内资源不会改变,如果方法不是幂等,有副作用的,每次请求出来的资源都不一样,也无从谈起缓存了。所以,对应到HTTP方法中,应该就是GET方法。
所以,包括浏览器直接url访问、html中link标签引用的资源、ajax调用的GET方法等触发GET的场景,都是可以配置缓存的。对于其他方法,是不能缓存的。
“新鲜度”问题
在定义缓存行为时,我们说需要去检查本地存储设备有资源“已缓存”的资源。如何检查资源是已缓存的呢?这就是“新鲜度”问题,也称为缓存策略。我们称缓存有效时是“新鲜的”。
同时,因为HTTP是无状态的,不能要求服务器去记录客户端哪些缓存,需要让客户端拥有一定的判断能力。
现代HTTP中,有两个简单的策略判断缓存“新鲜度”:文档过期(expiration)和服务器再验证(revalidation),在前端领域常常被称为强缓存(强制缓存)和协商缓存。
强缓存
强缓存即设置一个资源过期时间,检查资源是否过期,如果未过期,就直接视为缓存是新鲜的,就像在超市买牛奶看保质期一样,非常的简单。
在具体HTTP协议中有两个头字段实现此功能:
header | 描述 |
---|---|
Cache-Control:max-age | max-age 值定义了文档的最大使用期——从第一次生成文档到文档不再新鲜、无法使用为止,最大的合法生存时间(以秒为单位) Cache-Control: max-age=484200 |
Expires | 指定一个绝对的过期日期。如果过期日期已经过了,就说明文档不再新鲜了 Expires: Fri, 05 Jul 2002, 05:00:00 GMT |
此外Cache-Control还可以进行更细粒度的缓存控制,比如
-
控制HTTP结构中的其他节点是否对资源进行缓存(如代理服务器、隧道服务器等)
Cache-Control: public // 可以被其他HTTP节点缓存 Cache-Control: private // 只能被客户端缓存
-
是否允许使用过期缓存提供资源,比如当服务器不可用时
Cache-Control: must-revalidate // 就算服务器不可用,也不可提供过期资源 Cache-Control: max-statle [=<s>] // 可以提供过期资源,可以指定规则有效的秒数
-
完全控制客户端完全不进行任何缓存行为
Cache-Control: no-cache
协商缓存
协商缓存即资源可能已经过了过期时间,或者没设置过期时间,但是实际上服务器资源并没有更新,客户端可以使用缓存这样的场景。对于客户端,其信息是缺失的,需要和服务器进行协商才知道是否能够用缓存。
协商缓存不依赖过期时间,需要客户端和服务器用资源进行协商,那么就需要对资源进行唯一标志,客户端拿着资源的标志去问服务端,我这个编号的资源是否过期,按照服务器的响应来决定是否用缓存,若协商缓存成功,服务器返回304,客户端就可以拿缓存进行响应了。
在具体的HTTP协议中有两个头字段实现此功能:
header | 描述 |
---|---|
If-Modified-Since:<date> | 如果从指定日期之后文档被修改过了,就执行请求的方法。可以与Last-Modified服务器响应首部配合使用,只有在内容被修改后与已缓存版本有所不同的时候才去获取内容 |
If-None-Match:<tags> | 服务器可以为文档提供特殊的标签(参见ETag),而不是将其与最近修改日期相匹配,这些标签就像序列号一样。如果已缓存标签与服务器文档中的标签有所不同,If-None-Match 首部就会执行所请求的方法 |
从描述可以看出,这两个首部的标志策略不一样:
If-Modified-Since
是对资源用修改时间进行唯一标志,服务端需要在提供资源时附带修改时间Last-Modified
。但完全依赖修改时间的标志有些天然的缺陷:- 某些文档可能被操作,更改了最后修改时间(比如linux的touch指令),但实际包含的数据往往是一样的。
- 某些修改可能并不重要,不需要让所有的缓存数据都失效。
If-None-Match
不再依赖时间,是通过一套id的机制(实体标签ETag)来完成对资源的唯一标志,服务端需要在提供资源时带上资源的ETag
,由服务端自己编程实现什么时候缓存应该更新(更新资源的ETag)
工作流程
一般来说,缓存的工作流程可以概括为:
- 接收——缓存从网络中读取抵达的请求报文。
- 解析——缓存对报文进行解析,提取出 URL 和各种首部。
- 查询——缓存查看是否有本地副本可用,如果有,就获取一份副本。
- 新鲜度检测——缓存查看已缓存副本是否足够新鲜(强缓存);如果不是,就询问服务器是否有任何更新(协商缓存);
- 创建响应——缓存会用新的首部和已缓存的主体来构建一条响应报文。
- 发送——缓存通过网络将响应发回给客户端。