Http 缓存机制
Q:前端为什么要去了解http缓存机制呢?
- 在偏C端的业务系统中,往往对于软件的性能要求会更高一些,软件的易用性,响应速度,首屏打开速度都会影响用户的体验感
- 所以项目经理,产品经理都会对这个方面有较高的要求,更快的速度,更好的性能代表了更多的money
- 而很多可以优化的地方都要前端工程师对于http缓存机制有更加深刻认识和了解
Q:缓存机制只有http缓存么?使用它有啥好处?
答案是否定的,缓存机制到处都存在,为了提高性能,有客户端缓存,服务端缓存,代理服务器缓存等
但是对于我们前端而言,http缓存是我们前端可以接触到并实际使用的web性能优化手段
(1)减少网络带宽消耗
无论对于网站运营者或者用户,带宽都代表着金钱,过多的带宽消耗,只会便宜了网络运营商。当Web缓存副本被使用时,只会产生极小的网络流量,可以有效的降低运营成本。
(2)降低服务器压力
给网络资源设定有效期之后,用户可以重复使用本地的缓存,减少对源服务器的请求,间接降低服务器的压力。
(3)减少网络延迟,加快页面打开速度
答案是肯定的,对于最终用户,缓存的使用能够明显加快页面打开速度,达到更好的体验。
Q:http缓存机制有哪几种类型?
强制缓存
-
命中缓存
- 请求数据的时候命中缓存了(有缓存,且未失效),就直接从缓存中拿数据,不用与服务器进行交互
-
没有命中缓存
- 请求数据的时候没有命中(无缓存或者失效),就会向服务端发送请求,然后讲服务端返回的数据及缓存规则写入缓存
-
使用场景
- 持久性资源数据,可以使用该策略
-
-
status为灰色的是强制缓存,分为两种from memory cache 和 from disk cache,浏览器会优先读取内存中的数据
- from memory cache
- 存在于内存中
- 读取速度相对更快
- 进程关闭就会清空(关闭标签页就会清空)
- 一般存储js文件及图片资源
- from disk cache
- 存在于硬盘当中
- 读取速度相对较慢
- 进程关闭也不会清空
- 一般存储css文件
- from memory cache
-
配置参数
-
Expires
- 这个参数的配置属于http1.0的参数
- 值是一个时间绝对值(小于这个时间都会使用强制缓存)
-
Cache-Control
- 这个参数的配置属于http1.1的参数
- 值为几种情况
- public:所有内容都将被缓存(客户端和代理服务器都可缓存)
- private:所有内容只有客户端可以缓存,Cache-Control的默认取值
- no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
- no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
- max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
-
如果两个参数都存在的话,Cache-Control 优先级大于Expires
-
协商缓存
- 强制缓存失效后,浏览器携带缓存标识(一般为(Last-Modified / If-Modified-Since和Etag / If-None-Match))向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
- 协商缓存生效
- 服务器判断资源未更新,就会返回304状态码,客户端直接使用之前的缓存数据
- 协商缓存不生效
- 服务器判断资源更新,就会从数据库获取最新的数据,返回200状态码,客户端使用最新的数据并更新缓存
- Last-Modified / If-Modified-Since
- 值为一个标准时间,服务器会根据这个时间去比对资源
- 消耗性能小
- 精度只能到秒(密集请求可能会存在问题)
- Etag / If-None-Match
- 一个唯一的hash值,服务器会比较这个hash值是否一致
- 性能消耗较大
- 精度高,一般会优先考虑这个
用户行为对浏览器缓存的控制
- 地址栏访问,链接跳转是正常用户行为,将会触发浏览器缓存机制;
- F5刷新,浏览器会设置max-age=0,跳过强缓存判断,会进行协商缓存判断;
- ctrl+F5刷新,跳过强缓存和协商缓存,直接从服务器拉取资源。
实践
-
是否缓存依赖于接口响应头里面设置的参数(其实对于前端来说,操作有限,了解可以针对一些特定场景来解决问题)
-
下面是一个使用Nginx作为Web服务器的缓存配置
location / { # 其它配置 ... if ($request_uri ~* .*[.](js|css|map|jpg|png|svg|ico)$) { #非html缓存1个月 add_header Cache-Control "public, max-age=2592000"; } if ($request_filename ~* ^.*[.](html|htm)$) { #html文件使用协商缓存 add_header Cache-Control "no-cache"; }
-
配合webpack打包 contenthash的方案
hash 计算与整个项目的构建相关;
chunkhash 计算与同一 chunk 内容相关;(js和css在同一个chunk)
contenthash 计算与文件内容本身相关.(文件内容发生改变,contenthash就会改变)
module.exports = { output: { // 文件hash filename: '[name].[contenthash].js', }, optimization: { moduleIds: 'deterministic',//避免因为模块解析顺序发生变化而导致文件 hash 值改变 // runtime 单独打包为一个 chunk runtimeChunk: 'single', splitChunks: { cacheGroups: { // 第三方包打包为 vendors chunk,因为很少修改,有利长期缓存 vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all', }, }, }, }, };
-
大厂的解决方案(B站)
- html:no-cache
- JS文件:max-age=31536000(1年),文件命名带版本号或指纹信息,方便及时更新。
- CSS文件:max-age=31536000,文件命名带版本号或指纹信息,方便及时更新。
- 图片:max-age=31536000,文件命名带版本号或指纹信息,方便及时更新。
- XHR请求: no-cache
总结
注意点
- 缓存只能针对于get请求,对于post等对于数据库操作的请求类型是不支持的
- 缓存配置在响应体里,前端如果不涉及到后端的话,操作空间有限,只能与后端沟通哪些接口或者资源可以做缓存处理
- 缓存的目的是为了提供更好的体验和降低服务器压力,提高缓存命中率的同时,一定要保证数据的新鲜度