前言
关于页面性能优化, 浏览器缓存必定是一个绕不过的话题, 判断一个网站的性能最直观的就是看网页打开的速度, 而提高网页反应速度的一个方式就是使用缓存. 一个优秀的缓存策略可以缩短网页请求资源的距离, 减少延迟, 并且由于缓存文件可以重复利用, 还可以减少带宽, 降低网络负荷. 因此理解浏览器的缓存机制, 就显得尤为重要.
缓存类型
缓存在宏观上可以分成两类: 私有缓存和共享缓存. 共享缓存就是那些能被各级代理缓存的缓存. 私有缓存就是用户专享的, 各级代理不能缓存的缓存.
微观上可以分下面几类:
1. 浏览器缓存
缓存存在的意义就是当用户点击 back 按钮或是再次去访问某个页面的时候能够更快的响应. 尤其是在多页应用的网站中, 如果你在多个页面使用了一张相同的图片, 那么缓存这张图片就变得特别的有用. 浏览器先向代理服务器发起 web 请求, 再将请求转发到源服务器. 其中浏览器缓存包括强缓存和协商缓存, 下文有详细介绍. 本文主要侧重点就是针对于浏览器缓存.
2.CDN 缓存
CDN 缓存一般是由网站管理员自己部署, 为了让他们的网站更容易扩展并获得更好的性能. 通常情况下, 浏览器先向 CDN 网关发起 Web 请求, 网关服务器后面对应着一台或多台负载均衡源服务器, 会根据它们的负载请求, 动态将请求转发到合适的源服务器上. 从浏览器角度来看, 整个 CDN 就是一个源服务器, 从这个层面来说, 浏览器和服务器之间的缓存机制, 在这种架构下同样适用.
3. 代理服务器缓存
代理服务器是浏览器和源服务器之间的中间服务器, 浏览器先向这个中间服务器发起 Web 请求, 经过处理后(比如权限验证, 缓存匹配等), 再将请求转发到源服务器. 代理服务器缓存的运作原理跟浏览器的运作原理差不多, 只是规模更大.
4. 数据库缓存
数据库缓存是指, 当 web 应用的关系比较复杂, 数据库中的表很多的时候, 如果频繁进行数据库查询, 很容易导致数据库不堪重荷. 为了提供查询的性能, 将查询后的数据放到内存中进行缓存, 下次查询时, 直接从内存缓存直接返回, 提供响应效率.
5. 应用层缓存
应用层缓存是指我们在代码层面上做的缓存. 通过代码逻辑, 把曾经请求过的数据或资源等, 缓存起来, 再次需要数据时通过逻辑上的处理选择可用的缓存的数据.
强缓存
强缓存: 不会向服务器发送请求, 直接从缓存中读取资源, 在 chrome 控制台的 network 选项中可以看到该请求返回 200 的状态码, 并且 size 显示 from disk cache 或 from memory cache;
相关的 header:
1.Expires:response header 里的过期时间, 浏览器再次加载资源时, 如果在这个过期时间内, 则命中强缓存. 它的值为一个绝对时间的 GMT 格式的时间字符串, 比如 Expires:Thu,21 Jan 2018 23:39:02 GMT
2.Cache-Control : 这是一个相对时间, 在配置缓存的时候, 以秒为单位, 用数值表示. 当值设为 max-age=300 时, 则代表在这个请求正确返回时间 (浏览器也会记录下来) 的 5 分钟内再次加载资源, 就会命中强缓存. 比如 Cache-Control:max-age=300. 常见有以下六个属性值:
public: 所有内容都将被缓存(客户端和代理服务器都可缓存)
private: 所有内容只有客户端可以缓存, Cache-Control 的默认取值
no-cache: 客户端缓存内容, 但是是否使用缓存则需要经过协商缓存来验证决定. 需要注意的是, no-cache 这个名字有一点误导. 设置了 no-cache 之后, 并不是说浏览器就不再缓存数据, 只是浏览器在使用缓存数据时, 需要先确认一下数据是否还跟服务器保持一致.
no-store: 所有内容都不会被缓存, 即不使用强制缓存, 也不使用协商缓存
max-age:max-age=xxx (xxx is numeric)表示缓存内容将在 xxx 秒后失效
s-maxage(单位为 s): 同 max-age, 只用于共享缓存(比如 CDN 缓存). 比如, 当 s-maxage=60 时, 在这 60 秒中, 即使更新了 CDN 的内容, 浏览器也不会进行请求. 也就是说 max-age 用于普通缓存, 而 s-maxage 用于代理缓存. 如果存在 s-maxage, 则会覆盖掉 max-age 和 Expires header.
cache-control
简单概括: 其实这两者差别不大, 区别就在于 Expires 是 http1.0 的产物, Cache-Control 是 http1.1 的产物, 两者同时存在的话, Cache-Control 优先级高于 Expires; 在某些不支持 HTTP1.1 的环境下, Expires 就会发挥用处. 所以 Expires 其实是过时的产物, 现阶段它的存在只是一种兼容性的写法. 强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段, 而不关心服务器端文件是否已经更新, 这可能会导致加载文件不是服务器端最新的内容, 那我们如何获知服务器端内容较客户端是否已经发生了更新呢? 此时我们需要协商缓存策略.
协商缓存
协商缓存: 向服务器发送请求, 服务器会根据这个请求的 request header 的一些参数来判断是否命中协商缓存, 如果命中, 则返回 304 状态码并带上新的 response header 通知浏览器从缓存中读取资源; 另外协商缓存需要与 cache-control 共同使用.
相关的 header:
1.Last-Modified 和 If-Modified-Since
当第一次请求资源时, 服务器将资源传递给客户端时, 会将资源最后更改的时间以 "Last-Modified: GMT" 的形式加在实体首部上一起返回给客户端.
Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
客户端会为资源标记上该信息, 下次再次请求时, 会把该信息附带在请求报文中一并带给服务器去做检查, 若传递的时间值与服务器上该资源最终修改时间是一致的, 则说明该资源没有被修改过, 直接返回 304 状态码, 内容为空, 这样就节省了传输数据量 . 如果两个时间不一致, 则服务器会发回该资源并返回 200 状态码, 和第一次请求时类似. 这样保证不向客户端重复发出资源, 也保证当服务器有变化时, 客户端能够得到最新的资源. 一个 304 响应比一个静态资源通常小得多, 这样就节省了网络带宽.
Last-Modified 和 If-Modified-Since
但 last-modified 存在一些缺点:
某些服务端不能获取精确的修改时间
文件修改时间改了, 但文件内容却没有变
既然根据文件修改时间来决定是否缓存尚有不足, 能否可以直接根据文件内容是否修改来决定缓存策略?----ETag 和 If-None-Match
2.ETag 和 If-None-Match
Etag 是上一次加载资源时, 服务器返回的 response header, 是对该资源的一种唯一标识, 只要资源有变化, Etag 就会重新生成. 浏览器在下一次加载资源向服务器发送请求时, 会将上一次返回的 Etag 值放到 request header 里的 If-None-Match 里, 服务器只需要比较客户端传来的 If-None-Match 跟自己服务器上该资源的 ETag 是否一致, 就能很好地判断资源相对客户端而言是否被修改过了. 如果服务器发现 ETag 匹配不上, 那么直接以常规 GET 200 回包形式将新的资源 (当然也包括了新的 ETag) 发给客户端; 如果 ETag 是一致的, 则直接返回 304 知会客户端直接使用本地缓存即可.
ETag 和 If-None-Match
两者之间对比:
首先在精确度上, Etag 要优于 Last-Modified.Last-Modified 的时间单位是秒, 如果某个文件在 1 秒内改变了多次, 那么他们的 Last-Modified 其实并没有体现出来修改, 但是 Etag 每次都会改变确保了精度; 如果是负载均衡的服务器, 各个服务器生成的 Last-Modified 也有可能不一致.
第二在性能上, Etag 要逊于 Last-Modified, 毕竟 Last-Modified 只需要记录时间, 而 Etag 需要服务器通过算法来计算出一个 hash 值.
第三在优先级上, 服务器校验优先考虑 Etag
缓存的机制
强制缓存优先于协商缓存进行, 若强制缓存 (Expires 和 Cache-Control) 生效则直接使用缓存, 若不生效则进行协商缓存(Last-Modified / If-Modified-Since 和 Etag / If-None-Match), 协商缓存由服务器决定是否使用缓存, 若协商缓存失效, 那么代表该请求的缓存失效, 返回 200, 重新返回资源和缓存标识, 再存入浏览器缓存中; 生效则返回 304, 继续使用缓存. 具体流程图如下:
缓存的机制
用户行为对浏览器缓存的影响
1. 地址栏访问, 链接跳转是正常用户行为, 将会触发浏览器缓存机制;
2.F5 刷新, 浏览器会设置 max-age=0, 跳过强缓存判断, 会进行协商缓存判断;
3.ctrl+F5 刷新, 跳过强缓存和协商缓存, 直接从服务器拉取资源.
参考
浅谈 web 缓存 http://www.alloyteam.com/2016/03/discussion-on-web-caching/
缓存详解 https://juejin.im/post/5a6c87c46fb9a01ca560b4d7