校导李凡〡浏览器缓存那些事儿
计算机科学中最难的两件事是缓存失效和命名。
-- Phil Karlton
今天我们来简要了解一下浏览器的缓存问题。
浏览器缓存是指当浏览器请求一个资源的时候,会在本地保存一份副本,当下一个请求来到的时候,如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是向源服务器再次发送请求。
一浏览器缓存的作用
1. 减少网络带宽消耗:无论对于网站运营者或者用户,带宽都代表着金钱,过多的带宽消耗,只会便宜了网络运营商。当Web缓存副本被使用时,只会产生极小的网络流量,可以有效的降低运营成本。
2. 降低服务器压力:给网络资源设定有效期之后,用户可以重复使用本地的缓存,减少对源服务器的请求,间接降低服务器的压力。同时,搜索引擎的爬虫机器人也能根据过期机制降低爬取的频率,也能有效降低服务器的压力。
3. 减少网络延迟,加快页面打开速度:带宽对于个人网站运营者来说是十分重要,而对于大型的互联网公司来说,可能有时因为钱多而真的不在乎。那Web缓存还有作用吗?答案是肯定的,对于最终用户,缓存的使用能够明显加快页面打开速度,达到更好的体验。
二浏览器端的缓存规则
真正的浏览器工作的时候并不是将完整的内容保存在本地,各种浏览器都有不同的方式,譬如firefox是一种类似innodb的方式存储的key value 的模式,在地址栏中输入 about:cache 可以看见缓存的文件,chrome会把缓存的文件保存在一个叫User Data的文件夹下。
浏览器缓存有一套自己的规则,可以用来决定何时使用缓存,何时从服务器上获取资源,这些规则是在HTTP协议头和HTML页面的Meta标签中定义的。
1. 响应头明确说明,我不想被缓存,则不会被缓存;
2. 如果请求信息是需要认证或者安全加密的(如: HTTPS),相应内容也不会被缓存;
3. 缓存如果有以下表现,则认为是fresh新鲜的(无需检查源服务器,直接发送给客户端),则内容缓存直取,绕过源服务器。
★ 含有完整的过期时间和寿命控制头信息,并且内容仍在保鲜期内。
★ 缓存最近已展现,并且在不久前修改。
4. 若内容陈旧,则会要求源服务器做验证 validate ,或者告诉缓存其拷贝副本是否是OK的。
5. 特定情况下——例如,断网了,之前有过的响应缓存直取而不检查源服务器。 总而言之,新鲜度 freshness 和校验 validation 是确定缓存内容是否可用的两个维度。如果要展示的足够新,直接使用缓存;如果检测发现展示内容并未变化,则会直接使用缓存。下面两张图很清晰的描述了浏览器发起请求时决定是否使用缓存的机制:
三浏览器缓存的控制
1. 使用 Meta 标签控制页面缓存
前端攻城狮可以在HTML页面的节点中加入标签,代码如下:
上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。使用上很简单,但只有部分浏览器可以支持,而且所有缓存代理服务器都不支持,因为代理不解析HTML内容本身。所以并不推荐使用这种方式控制页面缓存。
2.使用 HTTP 协议头控制页面缓存
在HTTP请求和响应的消息报头中,常见的与缓存有关的消息报头有以下这些:
Cache-Control 与 Expires
Cache-Control 与 Expires 的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过 Cache-Control 的选择更多,设置更细致,如果同时设置的话,其优先级高于 Expires。
Last-Modified/ETag 与 Cache-Control/Expires
配置 Last-Modified/ETag 的情况下,浏览器再次访问统一 URI 的资源,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器会只发送一个304回给浏览器,告诉浏览器直接从自己本地的缓存取数据;如果修改过那就整个数据重新发给浏览器。
Cache-Control/Expires 则不同,如果检测到本地的缓存还是有效的时间范围内,浏览器直接使用本地副本,不会发送任何请求。两者一起使用时,Cache-Control/Expires 的优先级要高于 Last-Modified/ETag。即当本地副本根据 Cache-Control/Expires 发现还在有效期内时,则不会再次发送请求去服务器询问修改时间(Last-Modified)或实体标识(Etag)了。
一般情况下,使用 Cache-Control/Expires 会配合 Last-Modified/ETag 一起使用,因为即使服务器设置缓存时间, 当用户点击刷新按钮时,浏览器会忽略缓存继续向服务器发送请求,这时 Last-Modified/ETag 将能够很好利用304,从而减少响应开销。
Last-Modified与ETag
你可能会觉得使用 Last-Modified 已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要 Etag(实体标识)呢?HTTP1.1 中 Etag 的出现主要是为了解决几个 Last-Modified 比较难解决的问题:
★ Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的新鲜度。
★ 如果某些文件会被定期生成,当有时内容并没有任何变化,但 Last-Modified 却改变了,导致文件没法使用缓存。
★ 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形。
Etag 是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified 与ETag 是可以一起使用的,服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回304。
四浏览器缓存机制在实际开发中的应用
服务器端可以设置缓存规则,但在服务器不能掌控的地方也许会出现一些意外。
1. 缓存会被挤出。
所谓缓存被挤出,因为浏览器的缓存空间是有限的,所有网站希望缓存的文件都塞在里面,形成一个先进先出的队列。所以你即使设置了20年的缓存时间,也基本上是不可能保证有那么久的生命期。而且用户也有可能主动清除浏览器缓存,某些系统清理软件也可能清理浏览器缓存。
2. 文件有可能在运营商服务器上被劫持。
第二个问题是用户的宽带运营商为了提高速度,可能会在自己某节点服务器上缓存你的文件(比如 style.css?v1),好处是当用户请求这个文件的时候,运营商无需来你的服务器上请求文件,而自己直接就给出了。 如果你的 querys string 更新了(style.css?v2),按照HTTP规范,这理应被视作一个新的文件。
但是运营商仍然可能会拿自己的缓存,而不是遵循规范。有点可恶对不对?这就是我们在用户量极大的情况下侦查到的情况,所以,为了保证更新的文件确定能下发到所有的用户,我们最好是在资源文件改变时,使用前端构建工具修改文件名,而不是仅修改 query string。
以下是业界公认的最佳规则:
对于动态生成的html页面使用HTTP头:Cache-Control: no-cache
对于静态html页面使用HTTP头:Last-Modified
其他所有的文件类型都设置Expires头,并且在文件内容有所修改的时候修改文件名
作者简介:李凡,英文名Levy,校导Web前端工程师,热衷钻研前端技术,对单页应用开发和前端工程化有着浓厚的兴趣。