当前位置: 首页 > 科技观察

前端静态资源缓存的最优解决方案和max-age的陷阱

时间:2023-03-13 23:18:41 科技观察

合理的使用缓存可以大大提高网站的性能优势,也可以节省带宽,降低服务器成本。但很多网站只对了一半或一半。如果是这样的话,他们根本就不会利用缓存。很大程度上,由于静态资源的竞争,依赖的静态资源会不同步。以下是最佳静态资源缓存实践的两个示例。1.资源内容不变+设置长时间max-age//设置缓存时间为1年Cache-Control:max-age=31536000资源内容不会改变,so。..浏览器/CDN可以缓存一年,期间不会出现资源问题。缓存的内容可以使用一年而无需请求服务器请求。第一天浏览器请求了三个资源/index-v1.js、/base-v1.css和/dog-v1.png。第二天,浏览器请求了/index-v2.js、/base-v2.css和/dog-v1.png这三个资源。这里注意:index.js和base.css与***日请求的版本号不同。一年后,浏览器再也没有请求过/index-v1.js、/base-v1.css、/dog-v1.png这三个资源,浏览器缓存会保存删除。所以在这个例子中,为了让缓存发挥最好的作用,你要做的不是改变文件的内容,而是改变资源的URL:每个静态资源URL都应该随着其内容的修改而改变。比如示例index-v1.js中的v1,你不需要对其命名做任何限制。它可以是版本号、最后修改日期或根据内容计算的哈希值。大多数服务器端框架都提供工具来实现这一点。同样,nodejs中也有很多优秀的库来实现这个功能,比如gulp-rev、webpack、fis3。2.对于频繁修改的内容,总是需要服务器认证Cache-Control:no-cache该URL下的资源内容可能会被频繁修改,所以。..未经服务器确认,任何本地缓存的内容版本都不可信任。***日第二天注意事项:无缓存不代表不缓存。意思是在使用缓存资源之前,必须经过服务器的校验(revalidate也可以实现这个功能)。no-store告诉浏览器不要缓存它。另外,must-revalidate并不代表需要重新认证。它的前提是资源还在max-age的缓存期内,否则必须重新认证。在这种模式下,您还可以将ETag(您选择的版本ID)或Last-modified日期添加到响应标头。客户端下次获取资源时,会通过If-None-Match(对应ETage)和If-Modified-Since(对应Last-Mofied)这两个请求头将值发送给服务端。如果服务器发现这两个值相等,则返回一个HTTP304。如果没有发送ETag和Last-Modified,那么服务器总是会返回完整的资源内容。但是这个方法有一个缺点,就是每次都会去服务器验证,涉及到网络提取,所以不如第一个例子,可以完全绕过网络。3.在频繁修改内容的静态资源上使用max-age并不少见。这种情况并不少见。例如,它实际上发生在github页面上。想象一下:/article//styles.css/script.js全部使用://十分钟内不需要重新认证,十分钟后需要重新认证Cache-Control:must-revalidate,max-age=600随着内容的修改,十分钟内url发生变化,浏览器会一直使用缓存的内容,而不是向服务器请求最新的资源。十分钟后,如果可用,请使用If-Modified-Since和If-None-Match重新验证服务器。第一个请求:六七分钟后:最终:这在测试中经常发生。但想象一下,在网络环境中,你永远不知道浏览器前坐着什么样的人。他很可能会乱用鼠标,破坏了浏览器的静态资源缓存机制,导致页面出现异样,而且很难追查。在上面的示例中,服务器实际上已经更新了HTML、CSS和JS,但是页面最终使用缓存中的旧HTML和JS,以及刚刚从服务器下载的最新CSS。那么就会出现多个静态资源版本不匹配的问题。通常,当我们对HTML进行重大更改时,我们可能会更改CSS文件以适应新的DOM结构,并更新JS以配置样式和DOM修改。这些资源都是相互依存的,但是携带缓存信息的HTTP头并不关心你有没有。最终,用户很有可能会得到一个/两个新版本的静态资源,而其他所有资源都是旧版本。max-age是相对于服务器响应时间的,所以如果同时请求上述所有资源,即使都设置了相同的max-age时长,它们之间仍然存在很小的竞争可能性(后all,部分资源先到先得,全部资源返回后返回)。如果你的某些页面不包含JS,或者包含不同的CSS,它们的缓存过期时间可能不同步。更恶心的是,浏览器会一直从缓存中删除和获取资源。它不知道这些资源中哪些是相互依赖的。只要缓存时间一过,就会毫不犹豫的删除一个,不会删除资源。删除此过时文件所依赖的其他资源。以上各种可能性加在一起,静态资源版本不匹配的概率会很大。不过幸运的是,我们还是有办法解决这个问题的:强制刷新浏览器或者清除缓存。强制浏览器刷新或者清除缓存后,请求的页面和页面中的所有资源都会忽略之前的max-age,去服务器端去做。重新认证。因此,如果用户因为max-age出现问题,只需要强制刷新或者清除缓存就可以解决问题。当然,强迫用户这样做只会降低他们的可信度,并认为您的网站不可靠。..使用serviceWorker可以减少出现此类错误的机会。ServiceWorker执行时机:注册serviceWorker:if(navigator.serviceWorker){navigator.serviceWorker.register('/serviceworker.js',{scope:'/'});}executeserviceworker.js:constversion='2';self.addEventListener('install',event=>{//由于系统会随时休眠SW,为了防止执行中断,需要使用event.waitUntil来捕获event.waitUntil(caches.open(`static-${version}`).then(cache=>cache.addAll(//不稳定文件或大文件加载//...),cache.addAll([//稳定文件或小文件加载'/styles.css','/script.js'])););});self.addEventListener('activate',event=>{//...deleteoldcaches...});self.addEventListener('fetch',event=>{event.respondWith(caches.match(event.request)).then(响应=>响应||获取(事件.请求)));});缓存脚本和样式。如果有匹配的缓存,就从缓存中获取,如果没有,就从服务器中获取。如果我们修改JS/CSS,只需要修改版本,让serviceworker触发更新即可。您还可以跳过serviceworker中的缓存:self.addEventListener('install',event=>{event.waitUntil(caches.open(`static-${version}`).then(cache=>cache.addAll([newRequest('/styles.css',{cache:'no-cache'}),newRequest('/script.js',{cache:'no-cache'})])));});但非常不幸的是,safari或opera不支持缓存选项,最近只有firefox和chrome开始支持它。但是你可以这样做:self.addEventListener('install',event=>{event.waitUntil(caches.open(`static-${version}`).then(cache=>Promise.all(['/styles.css','/script.js'].map(url=>{//cache-bususingarandomquerystringreturnfetch(`${url}?${Math.random()}`).then(response=>{//failon404,500etcif(!response.ok)throwError('Notok');returncache.put(url,response);})}))));});上面代码中可以使用随机字符串,也可以使用哈希值。这有点像用javascript实现文章第一节的方法,只不过是用在了serverworker中。4、ServiceWorker和HTTP缓存也可以很好的共存。通过前面的例子可以看出,serviceworker可以很好的处理一些不好的缓存情况。但这只是做一些hack,最重要的是从根本上解决问题。正确使用缓存不仅可以更好地利用serviceworker,还可以在不支持serviceworker的浏览器(IE/Safari/Opera)上提高网站性能。另外,对你的CDN也有很大的好处。正确使用缓存可以大大简化serviceworker的代码:constversion='23';self.addEventListener('install',event=>{event.waitUntil(caches.open(`static-${version}`).then(cache=>cache.addAll(['/','/script-v3.js','/styles-v3.css','/dog-v3.jpg'])));});因此,我们可以使用第二小节(服务器重新验证)的方法来缓存根HTML页面。并使用***节中的方法(不同的内容使用不同的URL)来缓存其他资源。ServiceWorker每次更新都会请求网站的根HTML页面,其他资源只有在URL改变时才会下载,从而提高网站的性能。虽然serviceworker擅长提高网站的性能,但这并不是一个完整的解决方案。因此,它必须与HTTP缓存结合使用才能显着提高性能。5.max-age与“内容频繁修改但URL不变的静态资源”结合使用对内容频繁修改但URL不变的静态资源使用max-age并不是一个好主意一般意义上,但现实并非总是如此。假设一个页面的max-age为三分钟,不需要考虑这个页面静态资源的竞争(静态资源之间存在相互依赖,见第三节),所以这个页面没有静态资源依靠。在这种情况下,您可以根据需要使用max-age。但这也意味着网站的修改要等到三分钟后才能看到。但是,如果页面存在静态资源竞争,这种方法就不好用了。比如我现在有两篇文章A和B,我现在给A篇添加一个新章节,然后给B篇添加一个指向文章的超链接到新章节。然后我从文章B访问这个链接,如果文章A的max-age还没有过期,那么我会发现文章没有我访问的文章A的新章节。这时候只能等max-age过期或者强制浏览器刷新,或者清除缓存。因此,请务必谨慎使用此方法。正确使用缓存可以产生巨大的性能提升并节省服务器带宽。既支持版本号类型的静态资源缓存方式,也支持服务器重认证(no-cache,304)方式。如果你觉得自己够勇敢,你可以把max-age和“内容经常修改但URL不变的静态资源”混用,但是你得保证你的HTML中没有静态资源竞争。