速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

前端工程精粹(一):静态资源版本更新与缓存

  • 2013-09-12
  • 本文字数:4642 字

    阅读完需:约 15 分钟

每个参与过开发企业级 web 应用的前端工程师或许都曾思考过前端性能优化方面的问题。我们有雅虎 14 条性能优化原则,还有两本很经典的性能优化指导书:《高性能网站建设指南》、《高性能网站建设进阶指南》。经验丰富的工程师对于前端性能优化方法耳濡目染,基本都能一一列举出来。这些性能优化原则大概是在 7 年前提出的,对于 web 性能优化至今都有非常重要的指导意义。

然而,对于构建大型 web 应用的团队来说,要坚持贯彻这些优化原则并不是一件十分容易的事。因为优化原则中很多要求是与工程管理相违背的,比如“把 css 放在头部”和“把 js 放在尾部”这两条原则,我们不能让团队的工程师在写样式和脚本引用的时候都去修改一个相同的页面文件。这样做会严重影响团队成员间并行开发的效率,尤其是在团队有版本管理的情况下,每天要花大量的时间进行代码修改合并,这项成本是难以接受的。因此在前端工程界,总会看到周期性的性能优化工作,辛勤的前端工程师们每到月圆之夜就会倾巢出动根据优化原则做一次性能优化。

本文从一个全新的视角来思考 web 性能优化与前端工程之间的关系,通过解读百度前端集成解决方案小组(F.I.S)在打造高性能前端架构并统一百度 40 多条前端产品线的过程中所经历的技术尝试,揭示前端性能优化在前端架构及开发工具设计层面的实现思路。

性能优化原则及分类

笔者先假设本文的读者是有前端开发经验的工程师,并对企业级 web 应用开发及性能优化有一定的思考,因此我不会重复介绍雅虎 14 条性能优化原则。如果您没有这些前续知识,请移步这里来学习。

首先,我们把雅虎14 条优化原则,《高性能网站建设指南》以及《高性能网站建设进阶指南》中提到的优化点做一次梳理,按照优化方向分类,可以得到这样一张表格:

优化方向

优化手段

请求数量

合并脚本和样式表,CSS Sprites,拆分初始化负载,划分主域

请求带宽

开启GZip,精简JavaScript,移除重复脚本,图像优化

缓存利用

使用CDN,使用外部JavaScript 和CSS,添加Expires 头,减少DNS 查找,配置ETag,使AjaX 可缓存

页面结构

将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出

代码校验

避免CSS 表达式,避免重定向

表格1 性能优化原则分类

目前大多数前端团队可以利用 yui compressor 或者 google closure compiler 等压缩工具很容易做到“精简 Javascript”这条原则;同样的,也可以使用图片压缩工具对图像进行压缩,实现“图像优化”原则。这两条原则是对单个资源的处理,因此不会引起任何工程方面的问题。很多团队也通过引入代码校验流程来确保实现“避免 css 表达式”和“避免重定向”原则。目前绝大多数互联网公司也已经开启了服务端的 Gzip 压缩,并使用 CDN 实现静态资源的缓存和快速访问;一些技术实力雄厚的前端团队甚至研发出了自动 CSS Sprites 工具,解决了 CSS Sprites 在工程维护方面的难题。使用“查找 - 替换”思路,我们似乎也可以很好的实现“划分主域”原则。

我们把以上这些已经成熟应用到实际生产中的优化手段去除掉,留下那些还没有很好实现的优化原则。再来回顾一下之前的性能优化分类:

优化方向

优化手段

请求数量

合并脚本和样式表,拆分初始化负载

请求带宽

移除重复脚本

缓存利用

添加 Expires 头,配置 ETag,使 Ajax 可缓存

页面结构

将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出

表格 2 较难实现的优化原则

现在有很多顶尖的前端团队可以将上述还剩下的优化原则也都一一解决,但业界大多数团队都还没能很好的解决这些问题。因此,本文将就这些原则的解决方案做进一步的分析与讲解,从而为那些还没有进入前端工业化开发的团队提供一些基础技术建设意见,也借此机会与业界顶尖的前端团队在工业化工程化方向上交流一下彼此的心得。

静态资源版本更新与缓存

如表格 2 所示,“缓存利用”分类中保留了“添加 Expires 头”和“配置 ETag”两项。或许有些人会质疑,明明这两项只要配置了服务器的相关选项就可以实现,为什么说它们难以解决呢?确实,开启这两项很容易,但开启了缓存后,我们的项目就开始面临另一个挑战:如何更新这些缓存。

相信大多数团队也找到了类似的答案,它和《高性能网站建设指南》关于“添加 Expires 头”所说的原则一样——修订文件名。即:

最有效的解决方案是修改其所有链接,这样,全新的请求将从原始服务器下载最新的内容。

思路没错,但要怎么改变链接呢?变成什么样的链接才能有效更新缓存,又能最大限度避免那些没有修改过的文件缓存不失效呢?

先来看看现在一般前端团队的做法:

或者

大家会采用添加 query 的形式修改链接。这样做是比较直观的解决方案,但在访问量较大的网站,这么做可能将面临一些新的问题。

通常一个大型的 web 应用几乎每天都会有迭代和更新,发布新版本也就是发布新的静态资源和页面的过程。以上述代码为例,假设现在线上运行着 index.html 文件,并且使用了线上的 a.js 资源。index.html 的内容为:

这次我们更新了页面中的一些内容,得到一个 index.html 文件,并开发了新的与之匹配的 a.js 资源来完成页面交互,新的 index.html 文件的内容因此而变成了:

好了,现在要开始将两份新的文件发布到线上去。可以看到,index.html 和 a.js 的资源实际上是要覆盖线上的同名文件的。不管怎样,在发布的过程中,index.html 和 a.js 总有一个先后的顺序,从而中间出现一段或大或小的时间间隔。对于一个大型互联网应用来说即使在一个很小的时间间隔内,都有可能出现新用户访问。在这个时间间隔中,访问了网站的用户会发生什么情况呢?

  1. 如果先覆盖 index.html,后覆盖 a.js,用户在这个时间间隙访问,会得到新的 index.html 配合旧的 a.js 的情况,从而出现错误的页面。
  2. 如果先覆盖 a.js,后覆盖 index.html,用户在这个间隙访问,会得到旧的 index.html 配合新的 a.js 的情况,从而也出现了错误的页面。

这就是为什么大型 web 应用在版本上线的过程中经常会较集中的出现前端报错日志的原因,也是一些互联网公司选择加班到半夜等待访问低峰期再上线的原因之一。此外,由于静态资源文件版本更新是“覆盖式”的,而页面需要通过修改 query 来更新,对于使用 CDN 缓存的 web 产品来说,还可能面临 CDN 缓存攻击的问题。我们再来观察一下前面说的版本更新手段:

我们不难预测,a.js 的下一个版本是“1.0.1”,那么就可以刻意构造一串这样的请求“a.js?v=1.0.1”、“a.js?v=1.0.2”、……让 CDN 将当前的资源缓存为“未来的版本”。这样当这个页面所用的资源有更新时,即使更改了链接地址,也会因为 CDN 的原因返回给用户旧版本的静态资源,从而造成页面错误。即便不是刻意制造的攻击,在上线间隙出现访问也可能导致区域性的 CDN 缓存错误。

此外,当版本有更新时,修改所有引用链接也是一件与工程管理相悖的事,至少我们需要一个可以“查找 - 替换”的工具来自动化的解决版本号修改的问题。

对付这个问题,目前来说最优方案就是基于文件内容的hash版本冗余机制了。也就是说,我们希望工程师源码是这么写的:

但是线上代码是这样的:

其中”_82244e91”这串字符是根据 a.js 的文件内容进行 hash 运算得到的,只有文件内容发生变化了才会有更改。由于版本序列是与文件名写在一起的,而不是同名文件覆盖,因此不会出现上述说的那些问题。同时,这么做还有其他的好处:

  1. 线上的 a.js 不是同名文件覆盖,而是文件名 +hash 的冗余,所以可以先上线静态资源,再上线 html 页面,不存在间隙问题;
  2. 遇到问题回滚版本的时候,无需回滚 a.js,只须回滚页面即可;
  3. 由于静态资源版本号是文件内容的 hash,因此所有静态资源可以开启永久强缓存,只有更新了内容的文件才会缓存失效,缓存利用率大增;
  4. 修改静态资源后会在线上产生新的文件,一个文件对应一个版本,因此不会受到构造 CDN 缓存形式的攻击

虽然这种方案是相比之下最完美的解决方案,但它无法通过手工的形式来维护,因为要依靠手工的形式来计算和替换 hash 值,并生成相应的文件。这将是一项非常繁琐且容易出错的工作,因此我们需要借助工具。我们下面来了解一下 fis 是如何完成这项工作的。

首先,之所以有这种工具需求,完全是由 web 应用运行的根本机制决定的:web 应用所需的资源是以字面的形式通知浏览器下载而聚合在一起运行的。这种资源加载策略使得 web 应用从本质上区别于传统桌面应用的版本更新方式。为了实现资源定位的字面量替换操作,前端构建工具理论上需要识别所有资源定位的标记,其中包括:

  • css 中的 @import url(path)、background:url(path)、backgournd-image:url(path)、filter 中的 src
  • js 中的自定义资源定位函数,在 fis 中我们将其规定为 __uri(path)。
  • html 中的
2013-09-12 05:1146079

评论

发布
暂无评论
发现更多内容

该如何选择小游戏开发引擎?

Geek_2305a8

【YashanDB知识库】自关联外键插入数据时报错:YAS-02033 foreign key constraint violated parent key not found

YashanDB

yashandb 崖山数据库 崖山DB

华为云全域Serverless技术创新:全球首创通用Serverless平台被ACM SIGCOMM录用

华为云PaaS服务小智

云计算 Serverless 华为云

Maxon Cinema 4D Studio R21破解版安装教程 (C4D R21中文版下载)

Rose

一键登录,打造华为账号便捷新体验

HarmonyOS SDK

HarmonyOS

会声会影发光字体制作 会声会影字体怎么淡化退出 视频剪辑制作教程

阿拉灯神丁

字幕 会声会影2023 视频剪辑软件下载 视频剪辑软件

OpenTiny HUICharts 正式开源发布,一个简单、易上手的图表组件库

OpenTiny社区

Vue js eCharts OpenTiny

【Java】Jsoup 解析HTML报告

不在线第一只蜗牛

Java html

SD-WAN组网技术的九大应用场景

Ogcloud

SD-WAN 企业组网 SD-WAN组网 SD-WAN服务商 SDWAN

苹果电脑菜单栏日历工具 万年历 for mac- 日历以及天气预报工具

Rose

尤利西斯Ulysses for Mac 中文直装版 优秀的markdown写作软件 原创 白衣衫 1分钟前 编辑

Rose

Web3 游戏周报(7.28 - 8.03)

Footprint Analytics

链游

DNA序列分析软件 SnapGene 5破解版含SnapGene5激活工具 mac/win

Rose

淘宝商品详情API深度解读:商品标签与分类的洞察

代码忍者

API API 文档 API 测试

小程序容器技术对比,我们的App也能运行小程序了

Geek_2305a8

最佳实践:基于腾讯云 ES 的机器学习功能,实现一站式 NLP 语义聚合

腾讯云大数据

ES

一文弄懂Go语言的Context包,值得收藏!

左诗右码

Go

推荐3款免费且实用的数据库管理工具

EquatorCoco

数据库

腾讯云联合中科软发布 “保险行业一体化大数据解决方案” 共筑保司数据基座

腾讯云大数据

腾讯云 wedata

PostgreSQL的数据实时迁移到SelectDB

NineData

postgresql 复制 迁移 SelectDB ddl

Lightroom Classic 2023(lr2023)中文破解版 v12.4版本 支持Mac15系统 支持M1/M2

Rose

NFTScan | 07.29~08.04 NFT 市场热点汇总

NFT Research

NFT NFT\ NFTScan

打印报表--自由打印报表

小智数据

小智报表打印 小智开源报表 小智报表常见示例 纯前端js报表控件

[Mac/win永久激活版] Studio One 6中文版下载 Studio One 6许可证分享

Rose

加密市场“黑色星期一”:多重因素引发暴跌,后市如何演绎?

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

打印报表--标签打印报表

小智数据

自定义打印控件 小智开源报表控件 纯前端js报表控件 类excel样式报表 标签打印报表

我的 Electron 客户端被第三方页面入侵了

我再BUG界嘎嘎乱杀

黑客 网络安全 安全 Electron 网安

打印报表--客户订单打印报表

小智数据

小智开源报表 小智报表常见示例 纯前端js报表控件 类excel样式报表 客户订单打印报表

如何判断IP地址属于住宅IP还是机房IP

IPIDEA全球HTTP

代理IP

打印报表--分片重复报表

小智数据

小智报表打印 小智开源报表 小智报表常见示例 纯前端js报表控件 类excel样式报表

2025第十三届中国电子信息博览会(CITE深圳电子展)

AIOTE智博会

电子展 深圳电子展 电子信息展

前端工程精粹(一):静态资源版本更新与缓存_后端_张云龙_InfoQ精选文章