pfan123 / Articles

经验文章
169 stars 25 forks source link

浏览器缓存知识 #11

Open pfan123 opened 7 years ago

pfan123 commented 7 years ago

浏览器缓存知识

web缓存

缓存是指存储指定资源的一份拷贝,并在下次请求该资源时提供该拷贝的技术。当 web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。

Web缓存类型

在Web应用领域,Web缓存大致可以分为以下几种类型:

数据库缓存

Web应用,特别是SNS类型的应用关系比较复杂、数据库表繁多,需频繁进行数据库查询,很容易导致数据库不堪重荷。为了提高查询的性能,将查询后的数据放到内存中进行缓存,方便下次查询直接从内存缓存返回,快速响应。比如常用的缓存方案 memcachedredis

服务器端缓存

代理服务器是浏览器和源服务器之间的中间服务器,浏览器先向这个中间服务器发起Web请求,经过处理后(比如权限验证,缓存匹配等),再将请求转发到源服务器。代理服务器缓存的运作原理跟浏览器的运作原理差不多,只是规模更大。可以把它理解为一个共享缓存,不只为一个用户服务,一般为大量用户提供服务,因此在减少相应时间和带宽使用方面很有效,同一个副本会被重用多次。常见代理服务器缓存解决方案有 Squid 等。

CDN(Content delivery networks)缓存,也叫网关缓存、反向代理缓存。CDN缓存一般是由网站管理员自己部署,为了让他们的网站更容易扩展并获得更好的性能。浏览器先向CDN网关发起Web请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态将请求转发到合适的源服务器上。虽然这种架构负载均衡源服务器之间的缓存没法共享,但却拥有更好的处扩展性。从浏览器角度来看,整个CDN就是一个源服务器。

浏览器缓存

浏览器缓存根据一套与服务器约定的规则进行工作,在同一个会话过程中会检查一次并确定缓存的副本足够新。如果你浏览过程中,比如前进或后退,访问到同一个图片,这些图片可以从浏览器缓存中调出而即时显现。

浏览器缓存有那些

1、HTTP 缓存是基于 HTTP 协议的浏览器文件级缓存机制

2、HTML5 Web SQL 这种方式部分主流浏览器支持,从2010年11月18日W3C宣布舍弃 Web SQL database 草案开始

3、HTML5 indexedDB 是一个为了能够在客户端存储可观数量的结构化数据,并且在这些数据上使用索引进行高性能检索的 API

4、Cookie 一般网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)

5、localStorage 是 HTML5 的一种新的本地缓存方案,目前用的比较多,一般用来存储ajax返回的数据,加快下次页面打开时的渲染速度

6、sessionStorage 和 localStorage 类似,但是浏览器关闭则会全部删除,api和localstorage相同,实际项目中使用较少。

7、Application Cache (AppCache) 接口设定浏览器应该缓存的资源放在 manifest 配置文件中并使得离线用户可用,不过目前已从 Web 标准中删除

8、cacheStorage是在 ServiceWorker 的规范中定义的,可以保存每个serverWorker申明的cache对象

浏览器缓存存放位置

1、memory cache

按照操作系统的常理:先读内存,再读硬盘。几乎所有的网络请求资源都会被浏览器自动加入到 memory cache 中。但是也正因为数量很大但是浏览器占用的内存不能无限扩大这样两个因素,memory cache 注定只能是个“短期存储”。常规情况下,浏览器的 TAB 关闭后该次浏览的 memory cache 便告失效 (为了给其他 TAB 腾出位置)。而如果极端情况下 (例如一个页面的缓存就占用了超级多的内存),那可能在 TAB 没关闭之前,排在前面的缓存就已经失效了。 memory cache 机制保证了一个页面中如果有两个相同的请求 (例如两个 src 相同的 ,两个 href 相同的 )都实际只会被请求最多一次,避免浪费。 在从 memory cache 获取缓存内容时,浏览器会忽视例如 max-age=0, no-cache 等头部配置。 如果站长是真心不想让一个资源进入缓存,就连短期也不行,那就需要使用 no-store。 思考:刚才提到 几乎所有的网络请求资源都会被浏览器自动加入到 memory cache 中,为什么? preloader,在2007年之前,许多情况下,浏览器某些元素的解析和执行可能会影响紧随其后的资源,浏览器停止并等待资源完成下载,然后再获取下一个资源。这意味着如果一个页面包含多个JavaScript资源或外部CSS资源,在它们之后的元素,解析器将暂停并等待每个外部资源完成下载,然后再获取下一行内容。随着页面越来越繁琐的JavaScript,对性能的影响是巨大的,并且是灾难性的。所以在资源解析执行时候,网络状态是空闲的,能不能边解边请求资源呢,在2008年,ie/谷歌等浏览器便有了相关的机制(预加载器机制),预加载的资源都是放在memory cache中的。

2、disk cache

存储在硬盘上的缓存会严格根据 HTTP 头信息中的各类字段来判定哪些资源可以缓存,哪些资源不可以缓存;哪些资源是仍然可用的,哪些资源是过时需要重新请求的。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。绝大部分的缓存都来自 disk cache。 凡是持久性存储都会面临容量增长的问题,disk cache 也不例外。在浏览器自动清理时,每个浏览器都会识别“最老的”和“最可能过时的”资源。

3、Service Worker

Service Worker 是 Chrome 团队提出和力推的一个 WEB API,用于给 web 应用提供高级的可持续的后台处理能力。 service worker 能够操作的缓存是有别于浏览器内部的 memory cache 或者 disk cache。它是独立于当前页面的一段运行在浏览器后台进程里的脚本。大家可以把 Service Worker 理解为一个介于客户端和服务器之间的一个代理服务器。在 Service Worker 中我们可以做很多事情,比如拦截客户端的请求、向客户端发送消息、向服务器发起请求等等,离线资源缓存只是它的作用之一。 这个缓存是永久性的,即关闭 TAB 或者浏览器,下次打开依然还在(而 memory cache 不是)。有两种情况会导致这个缓存中的资源被清除:手动调用 API cache.delete(resource) 或者容量超过限制,被浏览器全部清空。 Service Worker 特点 网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost) 运行于浏览器后台,可以控制打开的作用域范围下所有的页面请求 单独的作用域范围,单独的运行环境和执行线程不能操作页面 DOM,但可以通过事件机制来处理。

HTTP 缓存分类

1.非验证性缓存,或者称为强缓存,用 Cache-Control 、 Expires 、 Pragma(比较老的版本兼容写法) 来控制,其特点是一旦有效就在有效期内不会发任何请求到服务器

2.验证性缓存,也叫协商缓存,用 ETag 、 Last-Modified 、 If-None-Match 、 If-Modified-Since 来控制,其特点是会发一个请求给服务器来确认缓存是否有效,如果有效就返回 304 ,省去传输内容的时间。

从描述上很容易看出来,非验证性缓存的优先级是高于验证性缓存的,因为有验证性缓存存在就根本不会发请求,自然也没有什么 If-None-Match 之类的东西出现的机会了。

目前,浏览器功能越来越强大,我们可以在chrome中输入 chrome://view-http-cache/,查看浏览器缓存情况,方便理解。

非验证性缓存

HTTP 1.0: 基于Pragma和Expires的缓存实现

在 http1.0 时代,给客户端设定缓存方式可通过两个字段——“Pragma”和“Expires”来规范。目前这两个字段早可抛弃,但为了做http协议的向下兼容,我们还是可以看到很多网站依旧会带上这两个字段。

Pragma

当该字段值为“no-cache”的时候(事实上现在RFC中也仅标明该可选值),会知会客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。

注意,Pragma优先级比较高,使用HTTP/1.0的缓存将忽略Expires和Cache-Control头

Expires

ExpiresRFC 2616(HTTP/1.0)协议中和网页缓存相关字段,用来控制缓存的失效日期。 Expires 头指定了一个日期/时间, 在这个日期/时间之后,HTTP响应被认为是过时的; 无效的日期,比如 0, 代表着一个过去的事件,即该资源已经过期了。

注意: Expires因为是对时间设定的,且时间是Greenwich Mean Time (GMT),而不是本地时间,所以对时间要求较高。

Cache-Control

HTTP1.1新增了 Cache-Control 来定义缓存过期时间。Cache-Control在 HTTP 响应头中(通用首部字段),用于指示代理和 UA 使用何种缓存策略。 比如 no-cache 为不可缓存、private 为仅 UA 可缓存,public 为大家都可以缓存。报文中同时出现了Expires 和 Cache-Control,会以 Cache-Control 为准。在RFC中规范了 Cache-Control 的格式为:

"Cache-Control" ":" cache-directive

常用 cache-directive 值

cache-directive 说明
public 所有内容都将被缓存(客户端和代理服务器都可缓存)
private 内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)
no-cache 必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache 会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。
no-store 所有内容都不会被缓存到缓存或 Internet 临时文件中
must-revalidation/proxy-revalidation 如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证
max-age=xxx (xxx is numeric) 缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高

常见 Response 内的 Cache-Control

# 允许客户端进行缓存,并且,需要立即进行请求服务器验证资源是否过期
cache-control: no-cache

#直接禁止浏览器以及所有中间缓存存储任何版本的返回响应,每次用户请求该资产时,都会向服务器发送请求
cache-control: no-store

# 表示 1000 秒内,浏览器需要这资源时,直接从缓存区内拿,而不用重新发请求
# 当过期时,发送「条件 GET 请求」
cache-control: max-age: 1000

# 经常会看到这样写
cache-control: max-age: 0, must-revalidate

# 其实等同于
cache-control: no-cache

验证性缓存

协商缓存会根据[last-modified/if-modified-since]或者[etag/if-none-match]来进行判断缓存是否过期。

Last-Modified

服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端。

客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码即可。

缺点:1.保存的时间是以秒为单位的,1秒内多次修改是无法准确捕捉到;2.各机器读取到的时间不一致,导致出现误差的可能性。

传递标记的最终修改时间的请求报文首部字段一共有两个:

当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。

告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。

ETag

ETag HTTP响应头是资源的特定版本的标识符。ETag是http协议提供的若干机制中的一种Web缓存验证机制,并且允许客户端进行缓存协商。生成ETag常用的方法包括对资源内容使用抗碰撞散列函数,使用最近修改的时间戳的哈希值,甚至只是一个版本号。

ETag能够解决last-modified的一些缺点,但是etag每次服务端生成都需要进行读写操作,而last-modified只需要读取操作,从这方面来看,etag的消耗是更大的。

和last-modified一样,浏览器会先发送一个请求得到ETag的值,然后再下一次请求在request header中带上if-none-match:[保存的etag的值]。通过发送的etag的值和服务端重新生成的etag的值进行比对,如果一致代表资源没有改变,服务端返回正文为空的响应,告诉浏览器从缓存中读取资源。

If-None-Match: ETag-value

If-None-Match: "56fcccc8-1699"

告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。

If-Match: ETag-value

告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。

缓存头部对比

头部 优势和特点 劣势和问题
Expires 1、HTTP 1.0 产物,可以在HTTP 1.0和1.1中使用,简单易用。
2、以时刻标识失效时间。
1、时间是由服务器发送的(UTC),如果服务器时间和客户端时间存在不一致,可能会出现问题。
2、存在版本问题,到期之前的修改客户端是不可知的。
Cache-Control 1、HTTP 1.1 产物,以时间间隔标识失效时间,解决了Expires服务器和客户端相对时间的问题。
2、比Expires多了很多选项设置。
1、HTTP 1.1 才有的内容,不适用于HTTP 1.0 。
2、存在版本问题,到期之前的修改客户端是不可知的。
Last-Modified 1、不存在版本问题,每次请求都会去服务器进行校验。服务器对比最后修改时间如果相同则返回304,不同返回200以及资源内容。 1、只要资源修改,无论内容是否发生实质性的变化,都会将该资源返回客户端。例如周期性重写,这种情况下该资源包含的数据实际上一样的。
2、以时刻作为标识,无法识别一秒内进行多次修改的情况。
3、某些服务器不能精确的得到文件的最后修改时间。
ETag 1、可以更加精确的判断资源是否被修改,可以识别一秒内多次修改的情况。
2、不存在版本问题,每次请求都会去服务器进行校验。
1、计算ETag值需要性能损耗。
2、分布式服务器存储的情况下,计算ETag的算法如果不一样,会导致浏览器从一台服务器上获得页面内容后到另外一台服务器上进行验证时发现ETag不匹配的情况。

缓存头部对比

从浏览器请求到展示资源的过程:

计算过期时间

用户行为对浏览器缓存的控制

地址栏访问重载

地址栏重新输入当前页面地址并按下回车也会当做刷新处理, 这意味着只有从新标签页或超链接打开时,才能观察到直接使用硬盘缓存的情况。

正常重新加载

按下刷新按钮或快捷键(在 MacOS 中是 Cmd+R)会触发浏览器的“正常重新加载”(normal reload), 此时浏览器会执行一次 Conditional GETCache-Control等缓存头字段会被忽略,并且带If-None-Match, If-Modified-Since等头字段。 此时服务器总会收到一次 HTTP GET 请求。 在 Chrome 中按下刷新,浏览器还会带如下请求头:

Cache-Control:max-age=0

强制重新加载

在 Chrome 中按下 Cmd+Shift+R (MacOS)可以触发强制重新加载(Hard Reload), 此时包括页面本身在内的所有资源都不会使用缓存。 浏览器直接发送 HTTP 请求且不带任何条件请求字段。

缓存运用情况

目前,各大公司对静态资源基本都采用 CDN 强缓存处理,通过加大 Cache-Control 的 max-age 有效值(例如京东设置的max-age为315360000一年),那输入URL按回车,我们会始终看到200 OK (form cache)缓存信息,知乎解释

强缓存的更新文件方式,主要是通过改变文件名,来更新文件缓存。常用手段,文件路径添加时间戳(?=xxx),文件名用md5,hash代替等。

但同时也会对一些不经常变更的静态资源做 ETag 协商缓存处理,以达到优化作用。

缓存配置实践

Nginx 缓存配置

location ~ .*\.(ico|svg|ttf|eot|woff)(.*) {
  proxy_cache               pnc;
  proxy_cache_valid         200 304 1y;
  proxy_cache_valid         any 1m;
  proxy_cache_lock          on;
  proxy_cache_lock_timeout  5s;
  proxy_cache_use_stale     updating error timeout invalid_header http_500 http_502;
  etag                      on;
  expires                   1y;
}

nginx proxy_cache

NodeJS 缓存配置

var oneYear = 60 * 1000 * 60 * 24 * 365;
app.use(express.static('staticFile', { maxAge: oneYear }));
response.setHeader('Cache-Control', 'max-age=31536000');

QA:遇到的缓存问题

1.from disk cache 和 from memory cache 区别

Chrome在高版本中缓存策略之后, 当看到的 200 from memory cache 就是验证性缓存 ,而 200 from disk cache 就是非验证性缓存,可通过两个浏览器访问对比得出

2.只设置Etag,那么为什么在 Chrome 下会有非验证性缓存呢?

没有设置 Cache-Control 这个头,其默认值是 Private ,在标准中明确说了:

Unless specifically constrained by a cache-control directive, a caching system MAY always store a successful response

如果没有 Cache-Control 进行限制,缓存系统可以对一个成功的响应进行存储

很显然, Chrome 是遵守标准的,它在没有检查到 Cache-Control 的时候对响应做了非验证性缓存,所以你看到了 200 from memory cache 同时 Safari 也是遵守标准的,因为标准只说了可以进行存储,而非应当或者必须,所以 Safari 不进行缓存也是合理的

我们可以理解为,没有 Cache-Control 的情况下,缓存不缓存就看浏览器高兴,你也没什么好说的。那么你如今的需求是“明确不要非验证性缓存”,则从标准的角度来说,你必须指定相应的 Cache-Control 头

查看浏览器缓存文件: chrome://view-http-cache/

3.常见Cache-Control 的 max-age 有效值设置

365天

Cache-Control:max-age = 315360000

30天

Cache-Control:max-age = 25920000

4.Response Header 中 Age 与 Date

Age表示命中代理服务器的缓存. 它指的是代理服务器对于请求资源的已缓存时间, 单位为秒.

Date指的是响应生成的时间,请求经过代理服务器时, 返回的Date未必是最新的, 通常这个时候, 代理服务器将增加一个Age字段告知该资源已缓存了多久。

5.Cache-Control设置强缓存计算过期时间

计算过期时间

过期日期 = Date + max-age ,所得出的日期

6.CAUTION: Provisional headers are shown

出现这个警告,是因为去获取该资源的请求其实并(还)没有真的发生,所以 Header 里显示的是伪信息,直到服务器真的有响应返回,这里的 Header 信息才会被更新为真实的。

参考资料:

Hypertext Transfer Protocol rfc2616 -- HTTP/1.1

IndexedDB 浏览器存储限制和清理标准

Caching Tutorial 缓存指南

Cache-control

Web缓存机制系列

浅谈 web 缓存

浅谈浏览器http的缓存机制

HTTP 缓存

使用 HTTP 缓存:Etag, Last-Modified 与 Cache-Control

合理使用 HTTP 缓存

NGINX缓存使用官方指南

ASP.NET Core 缓存技术 及 Nginx 缓存配置

网页性能优化:设置永久缓存

HTTP缓存控制小结

你应该知道的浏览器缓存知识