原文地址:Caching best practices & max-age gotchascss
正确的使用缓存能够带来巨大的性能提高,节省带宽,减小服务器消耗,可是不少网站对他们的缓存并无好好管理,致使相互依赖的资源不一样步(后面会介绍)。web
缓存的最佳实践大多数状况下是下面两种模式的其中一种:浏览器
Cache-Control: max-age=31536000
复制代码
max-age
的时间小的话就能够不咨询服务器直接使用在这个模式中,一些特殊的url内容永远不会发生改变,若是内容改变你能够修改url:缓存
<script src="/script-f93bca2c.js"></script>
<link rel="stylesheet" href="/styles-a837cb1e.css">
<img src="/cats-0e9a2ef4.jpg" alt="…">
复制代码
写url的地址随着内容的变化而变化.他可使版本号,修改时间,或者内容的hash。安全
不少框架有一些工具来生成必定规则的url。bash
可是,这个模式不适用一些相似文章或者博客的内容.这些url不能被版本化,他的内容会频繁的改变。服务器
Cache-Control: no-cache
复制代码
注意:no-cache
不表明"别缓存",他表示在使用缓存资源以前必需要通过服务器的验证.no-store
是告诉浏览器别缓存资源。另外must-revalidate
不是"必须通过验证"的意思,他的意思是:本地缓存的时间若是比max-age
小就能够直接用,否则的话要通过服务器验证。网络
在这个模式中你能够在响应头里添加ETag
(一个版本id)或者Last-Modified
.下次浏览器获取资源的,他会经过If-None-Match
把版本号带给服务器或者经过If-Modified-Since
把最后修改时间带给服务器,容许服务器经过HTTP 304告诉浏览器:就用你如今有的资源,这个是最新的.或者从新返回最新的资源文件。框架
这个模式每次都会发送请求,因此他没法像第一种模式同样完绕过网络请求这一步。dom
不少网站没法知足第一种模式要求资源的内容不能变,又不想要第二种模式每次都须要发送请求。而是选择中间的一种方式:一个很小的max-age
来配合会变化的内容.这是一个很是不明智的妥协。
max-age
一般是一个错误的选择很不幸,这种状况并很多见,好比Github的页面就有这样的状况.
想象一下:
/article/
/styles.css
/script.js
这些资源服务器都包含了这个响应头Cache-Control: must-revalidate, max-age=600
复制代码
接下来
If-Modified-Since
或者If-None-Match
的请求头这个模式在测试的时候不会有问题,可是在真实环境里就会有问题,同时这种问题很难被定位.就好像上面这个例子,事实上服务器须要同时更新HTML,CSS和JS,可是浏览器从缓存中获取了老的HTML和JS,从服务器获取了新的CSS.版本的不统一致使了问题的发生。
一般来讲.咱们更改了HTML的同时,也会同时修改CSS来装饰你的HTML结构.也许也会同时更改JS。这些资源是相互关联的,可是咱们的缓存头并有反应出这些.用户可能会同时得到一两个新版本的资源和一个旧版本的资源。
max-age
是和响应时间有关系的,因此若是上面这些资源在一个页面中被请求那么他们的过时时间粗略的算式同样的,可是仍然有可能他们的响应时间是有出入的.若是你有些页面只包含了三个相关联资源中的两个,那么他们的过时时间就会出现不一样步的状况。更糟的是,浏览器缓存常常会丢失缓存,而且缓存并不知道这三个资源是相互关联的,那么缓存根本不会在乎其中一个资源丢失。这些可能性加到一块儿,缓存版本不一样步的状况会颇有可能发生了。
对用户来讲,结果就是破坏layout或者同时破坏交互。这些隐藏小故障,可能会致使这个页面不可用。
幸亏,咱们还有办法能够拯救一下咱们的用户...
假设你有下面这些service worker:
const version = '2';
self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => cache.addAll([
'/styles.css',
'/script.js'
]))
);
});
self.addEventListener('activate', event => {
// 删除老的缓存
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});
复制代码
这个service worker作这几件事
若是咱们修改了咱们的JS或者CSS,就修改version
,使service workerc触发更新。然而,由于addAll
请求仍是会经过HTTP缓存(就像大多数的请求同样),咱们任然须要面对max-age
不一致致使的JS和CSS缓存不兼容的状况.
一旦这些资源被缓存,service worker在下次更新前都将提供不兼容的JS,CSS。咱们还要祈祷下次更新的时候不会出现不兼容的状况.
咱们可让service worker绕过缓存:
self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => cache.addAll([
new Request('/styles.css', { cache: 'no-cache' }),
new Request('/script.js', { cache: 'no-cache' })
]))
);
});
复制代码
可是不幸的是这个缓存选项在Chrome/Opera都不支持,只有在较新版本的Firefox支持,可是也能够用下面的兼容方案
self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => Promise.all(
[
'/styles.css',
'/script.js'
].map(url => {
// 使用随机数做为参数使他不使用缓存
return fetch(`${url}?${Math.random()}`).then(response => {
if (!response.ok) throw Error('Not ok');
return cache.put(url, response);
})
})
))
);
});
复制代码
上面的代码咱们经过随机数"绕过"了缓存,可是还能够作的更好一些,使用内容的hash来替代随机数。就有点像经过js从新实现了模式一,可是只能适用于能使用service worker的用户,而不是所有的浏览器或者CDN。
你能够在service worker里处理缓存,可是咱们最好仍是可以找到问题的根源而且解决。正确的使用缓存会让事情变的更简单,不只仅是对于service worker来讲,同时对那些不支持service worker的浏览器也有好处,而且能充分的利用CDN。
正确的使用缓存头也意味着咱们能够大大的简化service worker的更新工做:
const version = '23';
self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => cache.addAll([
'/',
'/script-f93bca2c.js',
'/styles-a837cb1e.css',
'/cats-0e9a2ef4.jpg'
]))
);
});
复制代码
咱们这里给根目录使用模式二(服务器验证缓存有效性),剩下的资源文件使用模式一(不会改变的内容).每次service worker更新会出发一次根目录文件的请求,可是剩余的文件只会在url改变的时候才会有网络请求。不论你的缓存版本怎么改变,都这样能够节省带宽,提升性能。
这相比本来的缓存来讲是一个巨大的提高,本来一个微小的改变都须要把资源整个下载。如今咱们能够下载一个相对比较小的资源文件,来更新一个大的web。
Service workers的使用能够做为一个缓存的增强,而不是一个替代缓存的解决方案。因此相比于缓存对着干,让他和缓存一块儿工做会带来更好的效果。
max-age
配合会变的资源仍是有好处的在有可能会改变的资源上使用max-age
一般不是一个好主意,可是这个也不是绝对的.好比这个网站咱们有些会改变的资源有三分钟的max-age
,由于这个页面没有任何相互依赖的资源文件使用相同的缓存模式(CSS,JS还有图片的URL使用的是模式一 - 不改变的内容),相互没有依赖关系的资源使用同一个模式。
这意味着,若是我够幸运能写出一篇很是流行的文章,个人CDN能够减轻我服务器的压力,可是我文章的更新可能会最长须要延迟三分钟才能被用户看到,这对于我来讲也是能够接受的。
若是我在文章中新增了一个在别的文章中须要跳转过来引用的小节,我就建立了可能会致使不同的相互关联的资源。用户可能点击了连接跳转过来发现并无发现新加的一小节内容.若是我想避免这样的事情发生,我须要更新第一个文章,更新CDN,等待三分钟,而后在另一个文章中加上连接.用这个模式要十分的当心。
正确的使用缓存能够代码巨大的性能提高,还能够节省带宽.对于不会改变的内容使用第一种模式,不然使用服务器验证是比较安全的.只有在你很是有把握的时候把max-age
和会改变的资源文件放在一块儿使用,可是你要确保你的资源文件没有相互关联性,或者相互关联的资源文件不会不一样步。