前言

今日早读文章由腾讯@CoyPan翻译授权分享。

@CoyPan,一名符合预期的前端工程师

正文从这开始~~

使用正确的缓存可以带来巨大的页面性能上的收益,节省带宽,减少服务器成本。但是许多网站并没有解决好他们的缓存问题,创造了一个race conditions,导致相互依赖的资源之间失去了同步。

绝大多数缓存的最佳实践,都属于下面两种模式:

模式一:不可变的内容 ,长时间的max-age

Cache-Control: max-age = 31536000
  • 同一个URL对应的内容永不改变

  • 浏览器/CDN 可以缓存这个资源长达一年的时间

  • 被缓存资源的存储时间小于max-age指定的秒数时,该资源可以直接被使用而无需经过服务器。


在这种模式下,你不会去改变特定url下的文件内容,你直接改变url:

 rel="stylesheet" href="/styles-a837cb1e.css"> src="/cats-0e9a2ef4.jpg" alt="…">

每一个URL都包含一个跟随文件内容变换的部分。这个部分可以是版本号,修改日期,或者文件内容的hash值。

大多数服务端框架都有工具可以简单的实现这个需求。Node.js下还有更轻量级的工具能够做到同样的事情,比如gulp-rev.

但是,这种模式不适合诸如文章、博客这样的场景。文章和博客的URL是不会有版本号的,而且他们的内容能够随时修改。说真的,如果我在文章中犯了拼写或者语法错误,那么我需要能够快速、频繁的修改文章内容。

模式二:可变的内容,总是向服务器发起校验

Cache-Control: no-cache
  • 同一个url对应的内容会改变

  • 任何本地缓存的版本都是不可信的,除非服务器校验通过


注意:no-cache并不意味着不缓存,而是使用缓存前必须请求服务端进行检查(或者说叫重新校验)。no-store告诉浏览器,根本不要缓存这个文件。同时,must-revalidate也不是说就『must-revalidate』,而是如果本地资源的缓存时间还没有超过设置的max-age的值,就可以直接使用本地资源,否则必须重新校验。

在这种模式下,你可以在响应头里添加一个ETag(你选择的版本ID)或者Last-Modified。客户端下一次请求资源时,会分别带上If-None-Match和If-Modified-Since,服务端会判断说:直接使用你已有的本地资源吧,他们是最新的。这就是最常见的:HTTP 304

如果没有带上ETag/Last-Modified,服务端会再次返回完成的内容。

这种模式总是会发起一个网络请求,而模式一是可以不用通过网络的。

使用模式一时,因为网络基础建设而导致的延时是很常见的,使用模式二时,也很容易遇到网络环境带来的延迟。取而代之的是中间的东西:一个短时间的max-age设置和可变的内容。这是一种十分糟糕的妥协。

对可变内容使用max-age通常是一个错误的选择

不幸的是,这种做法并非不常见。比如,Github pages就是这样的。

想象一下有以下三个url:

  • /article/

  • /styles.css

  • /scripts.js

服务端都是返回的:

Cache-Control: must-revalidate, max-age=600
  • url对应的内容是变了

  • 如果浏览器缓存了一个资源版本,但是没有到10分钟,会不经过服务器直接使用这个缓存的资源。

  • 否则发起一个网络请求,带上If-Modified-Since或者If-None-Match(如果可用)


这种模式在测试的时候看起来是可以的,但在现实中,会出问题,并且很难追踪。在上面的例子中,服务端确实已经更新了HTML, CSS 和JS,但是页面最终使用了缓存里的HTML,JS,CSS却是从服务端获取的最新的版本。资源版本不匹配导致了页面出错。

通常情况下,当我们对HTML进行重大更改时,我们还可能更改HTML对应的CSS结构,并更新JS以适应样式和内容的更改。这些资源是相互依赖的,但是缓存的header是无法描述这种依赖的。用户最终看到的,可能是一两个新版本的资源,和其他老的资源。

max-age和响应时间有关,因此,如果上述所有的资源都是在同一次访问中请求的,他们大概会在同一时间到期,但是仍然有很小的可能发生竞争。如果你的某些页面并不包含JS或者包含了不同的CSS,那么过期时间可能就不同步了。更糟糕的是,更糟糕的是,浏览器总是从缓存中删除东西,它不知道HTML、CSS和JS是相互依赖的,所以它会很高兴地删除一个而不是其他的。上述的情况,都可能会导致页面资源的版本不匹配。

对用户来说,他们最终会看到错误的页面布局和错误的页面功能,从细微的错误到完全不可用的内容。

谢天谢地,对用户来说还是有补救措施的。

  • 刷新可能会修复这个问题

如果页面作为刷新的一部分加载,浏览器会忽略max-age,向服务器进行验证。因此,如果用户遭遇了因为max-age而造成的错误,刷新是可以解决问题的。当然,强迫用户这样做会降低信任度,因为这会让你感觉到你的网站是不靠谱的。

  • service worker可能会延长这些bug的寿命

假设你有以下的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 => {// …delete old caches…});self.addEventListener('fetch', event => {event.respondWith(    caches.match(event.request).then(response => response || fetch(event.request)));});

这个service-worker

  • 缓存了script和style

  • 如果命中了缓存,就从缓存中取,否则发起网络请求

如果我们更改了CSS/JS,我们会修改service-worker中的版本号,触发service-worker的更新。但是,假如addAll发出的请求经过了HTTP缓存(和其他大多数缓存一样),我们也会进入到max-age的race condition,缓存不匹配的CSS、JS版本。

一旦他们被缓存了,我们将会一直看到不匹配的CSS和JS,直到我们下一次更新service-worker。而在下一次更新时,我们可能还会陷入另一个race condition。

你可以在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 => {// cache-bust using a random query stringreturn fetch(`${url}?${Math.random()}`).then(response => {// fail on 404, 500 etcif (!response.ok) throw Error('Not ok');return cache.put(url, response);})}))));});

在上述代码中,我用随机数来避免缓存,但是你可以更进一步,在构建的时候为内容增加一个hash值(和sw-precache做的事差不多)。这是一种在js层面的对模式一的实现,但是仅仅对service worker的使用者是有效的,而不是对所有的浏览器和你的CDN都有效。

service worker & http缓存可以同时使用,不要让他们冲突

正如你所见,你可以绕过service worker中糟糕的缓存,但是你最好解决根源的问题。正确的设置缓存能够让你在使用service worker的时候更加轻松,并且对那些不支持service worker的浏览器也是有好处的,还能让你充分的使用你的CDN。

正确的缓存头还意味着你可以大量简化server 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'])));});

在这里,我将使用模式2(服务器重新验证)缓存根页面,其余资源使用模式1(不可变内容)。每次service worker更新都将触发对根页面的请求,但只有当资源的URL发生更改时,才会下载其余资源。这很好,因为无论你是从以前的版本还是第10个版本更新,它都可以节省带宽并提高性能。

相对于本地应用来说,这是一个巨大的优势。在本地应用中,不管二进制内容有细微和巨大的改变,整个二进制内容都会被下载。而在这里,我们只需要一个小小的下载,就能更新巨大的web app.

service worker的工作最好是作为一个增强方案,而不是变通方案。所以预期与缓存抗争,不如好好利用缓存。

谨慎使用,max-age & 可变内容 也可以很有效

对于可变内容使用max-age一般情况下是一个错误的选择,但也不总是这样。比如,这个页面设置了一个3分钟的max-age. race condition在这个页面是不会成为问题的,因为这个页面没有任何遵循这一种模式的依赖(我的css,js,图片等都遵循模式1-不可变内容),依赖于此页的任何内容都不会遵循相同的模式。

这种模式意味着,如果我有幸写了一篇热门文章,我的cdn可以让我的服务器散热,而我能忍受用户需要花三分钟时间才看到文章更新。

这种模式不能随便使用。如果我在文章中添加了一个新的部分,并且将这个部分链接到一篇新的文章,那么我就创造了一个会争用的依赖项。用户可以单击链接,并在没有引用部分的情况下获取文章的副本。如果我想避免这种情况,我就得更新第一篇文章,刷新cdn, 等待3分钟,然后在另一篇文章中添加指向他的链接。是的…..你必须非常小心这种模式。

正确使用,缓存能极大的提高性能并且较少带宽消耗。对于任何容易更改的URL,都支持不可变的内容,否则在服务器重新验证时会使其安全。只有当你足够勇敢,并且你确信你没有可能会失去同步的依赖项时,再使用max-age和可变内容的模式。

关于本文
译者:@CoyPan
译文:https://mp.weixin.qq.com/s/yGTD8SZ2ZxJMtrbXoo3rBg
作者:@Jake
原文:https://jakearchibald.com/2016/caching-best-practices/

他曾分享过

【第1489期】关于JavaScript, NPM官方发布了2018年的回顾以及2019年的预测

为你推荐

【第1501期】浏览器往返缓存(Back/Forward cache)问题的分析与解决

【第1398期】一文读懂前端缓存

【第1250期】彻底理解浏览器的缓存机制

版本更新带来的缓存问题_【第1563期】缓存最佳实践 amp; maxage的陷阱相关推荐

  1. java系统缓存应用_著名java开源缓存系统 【zz】

    目前在学习缓存的一些开源代码,查询到 一些资料分享给大家 以下是几个著名java开源缓存系统的介绍: OSCacheOSCache是个一个广泛采用的高性能的J2EE缓存框架,OSCache能用于任何J ...

  2. 网卡清空缓存命令_怎么清除dns缓存 查看与刷新本地DNS缓存方法 (全文)

    由于近几日百事网更换了cdn加速服务商,导致不少地区朋友无法访问百事网,包括小编电脑也是经常打不开,目前主要可以通过清除dns缓存来解决.一般来说,电脑在第一次访问一个网站后,在一定时间内会有本地DN ...

  3. 文字层一点就变红_《蚂蚁前端研发最佳实践》文字稿

    以下是我在 2019.11.15成都全栈大会分享的文字稿,介绍了蚂蚁前端研发的最佳实践,其中我提取了三个比较重要的点,每个点都是我们实践和深入思考后的结果,希望能对大家有所启发,欢迎探讨. 开篇 准备 ...

  4. java jpa性能_[Java Performance] 数据库性能最佳实践 - JPA和读写优化

    数据库性能最佳实践 当应用须要连接数据库时.那么应用的性能就可能收到数据库性能的影响. 比方当数据库的I/O能力存在限制,或者因缺失了索引而导致运行的SQL语句须要对整张表进行遍历.对于这些问题.只相 ...

  5. idea资源包下创建资源包_资源包技巧和最佳实践

    idea资源包下创建资源包 今天是资源捆绑日. 通常,这是Java中最著名的国际化机制(i18n). 使用它应该很容易. 但是,弄污双手时会出现许多小问题. 如果您有相同的想法,则此文章适合您. 基本 ...

  6. eclipse插件开发_开发Eclipse插件的最佳实践

    在为IDE Eclipse环境开发插件时,您有几个设计注意事项. 这些注意事项可确保您: 不要锁定用户界面线程. 在不影响性能的情况下装饰用户界面. 在后台处理数据. 本教程讨论了如何利用这些设计注意 ...

  7. java 缓存分页_基于redis做缓存分页

    在实际业务中我们会将一些热数据缓存到redis里面,这时候数据量比较大的话,我们就要对这些热数据进行分页,分页的方式有2种: 第一:从redis拿出所有数据后,再做内存分页(不推荐),热点数据小的时候 ...

  8. ehcache 缓存丢失_求助,EhCache缓存中数据失效的问题!

    配置文件如下:plugins.add(new EhCachePlugin());//使用EhCache缓存public void afterJFinalStart() { new CacheThrea ...

  9. 结合webpack配置_前端 Webpack 工程化的最佳实践

    作者 | 阿里文娱前端开发专家 芃苏责编 | 屠敏头图 | CSDN 下载自视觉中国 引言 ▐ 前端构建工具的演变 回想在2015-2016年的时候,开发者们开始渐渐把视线从大量使用Task Runn ...

  10. webworker应用场景_典型应用场景 · OpenResty最佳实践-最新版 · 看云

    # 典型应用场景 可以这样说,任何一个开发语言.开发框架,都有它存在的明确目的,重心是为了解决什么问题.没有说我们学习一门语言或技术,就可以解决所有的问题.同样的,`OpenResty`的存在也有其自 ...

最新文章

  1. go语言中的float类型
  2. HDU 4946 Area of Mushroom 凸包
  3. 七个步骤,带你快速读懂 RPC 框架原理
  4. 12/100. Diameter of Binary Tree
  5. 西南科技计算机在线自测,西南科技大学2017春季第一学期高等数学1在线自测答案...
  6. 分析PCB技术印制电路板的可靠性设计(z)
  7. 如何设置单词第一个字母大写_大写一行中每个单词的第一个和最后一个字母
  8. [坐标]关于坐标系和投影的相关知识探讨[转]
  9. aws rds监控慢sql_如何使用Web控制台和AWS CLI停止AWS RDS SQL Server
  10. Kubernetes学习笔记(一):Kubernetes-1.7.x 创建TLS证书和秘钥
  11. 回复失恋男的来信(转)
  12. 第二季-专题3-汇编语言得玩转
  13. Win10鼠标右键菜单不显示怎么办?
  14. ir2110驱动占空比不能太高
  15. 易企秀手机html5场景源码,仿易企秀V15.1手机网页DIY制作工具完整版开源版源码修复采......
  16. 【代码质量】-阿里巴巴java开发手册(代码质量提升神器)学习笔记
  17. HTML、CSS实现手风琴效果
  18. 基于arduino、ros手柄控制机械臂
  19. 网课查题API接口(免费)
  20. 模拟开关和数字开关的区别

热门文章

  1. Atitit doc mng 文档管理总结目录1. 主要几大内容 12. 存储管理 22.1. 一般来说 ,文档存储在IM网盘note邮箱blog wiki等地 22.2. 文档格式与体
  2. 音频文件转码工具文档 目录 1. 音频文件转码 1 1.1. 简介 1 1.2. 转换命令示例 2 1.3. wav 文件转 16k 16bits 位深的单声道pcm文件 2 1.4. mp3 文件转
  3. Atitit 集合分组聚合操作sum count avg java版本groovy版本 目录 1. //按性别统计用户数 1 7. //按性别获取用户名称 1 16. //按性别求年龄的总和 1 2
  4. Atitit 近年来的软件与编程与技术趋势大盘点 and 2017 未来技术趋势attilax总结
  5. paip.语义分析--分词--常见的单音节字词 2_deDuli 单字词 774个
  6. PAIP.pdf使用
  7. 赵学军: 理想主义者的下一城
  8. (转)某期货系统漏洞致巨损二审在即 千万损失谁来买单?
  9. 如何做到数百万台车联网设备同时在线 0 故障
  10. 机器学习笔记(三十二):集成学习、随机森林