前端项目部署
Lancelote Lv2

大公司如何开发部署前端代码

多人协作模式下的前端代码开发、超大流量访问下的前端应用的部署

  • 多人协作开发
    这个比较基础、主要是代码管理方面,使用 git 或者 svn,按照一定的 flow,如从 dev 分支下切新分支进行开发、开发完成之后提测,发起合并请求,合并到测试分支,当测试完成之后继续提交合并请求,上线
  • 大型网站主站的前端应用部署
    分析一下,部署前端代码到底做了什么呢?
    主要做的事情就是将静态文件如 index.html、index.js、index.css 放到具体的静态资源服务器上,常见的如 nginx,随后重启服务,部署就完成了,用户要访问最新的应用,刷新页面就可以了,这个过程非常简单。
    但是问题来了,如果一个网站流量非常大,这样做的话就会出现,光是这三个文件(实际上远不止三个文件)的加载消耗的流量就是天量的数字,非常浪费性能。通常这种问题我们会想到利用浏览器的缓存机制来优化,事实上浏览器的缓存就是这么用的
    1. 304 协商缓存

      协商缓存还是存在一次与服务器通信过程,我们的目的就是彻底取消与服务器的通信,因此放弃

    2. 强缓存

      强缓存倒是不用通信了,但是不和服务器通信的话,如何更新资源呢,毕竟每次更新的目的都是要让用户去使用我们的产品,这时候可以用更新文件资源路径来让浏览器主动放弃缓存,每一次更新,都自动更新链接地址

    以上第二种方式可以达到我们的目的,但是也存在一个问题,我们每次更新不能修改所有的文件链接地址,需要知道当前的更新是哪个文件,并且只修改对应的文件地址就好了

    更新对应修改文件的链接地址

如何知道修改了哪个静态资源文件呢,也就是什么东西与文件内容有关呢,可以利用 Hash 算法,将文件内容 hash 成一个字符串内容,hash 算法可以根据每次不同的文件内容生成不同字符串,因此就达到了控制单文件粒度的目的。

这个对文件内容 hash 化的过程,通常是由构建工具完成的,如 webpack、vite、rollup 等等在打包 build 的时候就会处理好,继而将对应的文件路径添加到 html 中。

使用 CDN,内容分发网络,将静态资源和动态网页分集群部署,具体是将需要部署的网站主站页面 index.html(动态网页)部署到部署服务器中,将网页包含的静态资源(如 a.js,b.css)部署到 CDN 节点上,最后将 CDN 映射的域名链接添加到动态网页中。

  1. 先部署页面,后部署资源;那么在二者部署的间隔内,有用户访问页面(大型系统的主站每时每刻都有人访问),会在新的页面加载旧的资源,除非是手动刷新,否则在资源缓存过期之前,页面会一直执行错误
  2. 先部署资源,后部署页面;那么在二者部署的间隔内,有旧版本资源本地缓存的用户访问页面,请求的页面也是旧版本的,资源引用没有改变,页面展示正常,而没有本地缓存后者缓存过期的网站,就会出现旧版本页面访问新版本资源的情况,导致页面执行错误(一般是样式错位),除非等到页面部署完成,才能恢复正常。

以上问题说明,先部署都不行,都会有两者的部署间隔期,如果访问量不大的话,可以在访问低谷期部署,也就是半夜上线,这样对流量访问影响最小,但是大型网站,门户网站,主站这些流量很大且没有明显低谷期的网站,就不能通过这种方式发布了;因此以上的问题的根源就是发布的时候旧资源会被丢弃,导致访问不到对应的内容

内容有修改的文件的资源对其 hash 计算得到一个新的 hash 值,上传到 CDN 节点上,生成成一个新的文件链接发布到线上,不会覆盖已有的资源文件,上线过程中,先全量部署静态资源,再灰度部署页面。问题就解决的差不多了。

总结下来就是:

  • 配置超长时间的本地缓存,节省带宽,提高性能
  • 采用内容摘要作为内容更新的依据,精确控制更新的文件
  • 静态资源 CDN 部署
  • 更新资源发布路径采用滚动式发布,平滑升级

发布完之后,如果遇到发布代码有问题,需要马上回滚,最好是秒级会滚,应该如何做呢

重新打包部署不现实,因为这个时间太久了,很有可能用户已经访问了错误的页面,最好的方案是能够切换路由到上一个版本,从入口处解决,这个时候就需要在资源链接上增加一个版本号的概念:?version=1.x.x,通过指定渲染哪个版本号的资源就可以达到秒级回滚页面内容的效果。

这里的 version 号可以根据业务来确定,如每次部署都会有一个业务号,每次部署的业务号自增,需要回滚的时候,只需要根据版本号自动切换即可,这个切换最好是在网关处处理,这也意味着滚动式发布是生产环境的最适合的方案,具有容灾备份的效果。

以上是属于代码 build 之后进行 deploy 的过程,除此之外,部署的过程还有代码完成之后到 build 这个过程,下面就介绍一下这个过程:CiCd

ci/cd:持续交付/持续部署,意思是新增代码后的自动校验格式,打包,构建,跑单元测试,提供单元测试覆盖率,出具报告,之后在进行代码自动部署,目的是简化重复的工作,且不会出错。

常见方案:

  • Jenkins
  • Gitlab-ci actions
  • Docker + K8s
  • 自己搭建构建服务器

线上部署期间,用户长时间没有访问网页等各种情况,有一定概率出现错误Uncaught ChunkLoadError: Loading chunk <CHUNK_NAME> failed. 这个错误是说无法加载资源文件,可能是资源文件名错误,这种情况会导致网页白屏

原因在于现在前端构建工具 webpack 打包出来的项目,主入口(index.html)默认不缓存,其他文件长期缓存,缓存的文件通过改变文件名(hash)来更新,所以在部署期间长期没有访问,会出现请求的资源路径已被删除,或者虽然路径没变,但是 chunk 的内容改变了,导致出现的白屏,

有一下方案:

  • 缓存所有版本的文件,空间占用太大
  • 打包出来的文件不做缓存,对服务器压力很大
  • 通过 websocket 等轮询的方式主动告知浏览器更新版本,(也就是刷新页面)需要后端支持
  • 对于报错的文件重试,超过一定次数白屏,主动告知用户刷新页面

综上,采用最后一种方案

缓存重试一定次数后弹窗提示用户刷新页面,借助 webpack-retry-chunk-load-plugin 插件和 html-tag-attribute-plugin 插件配合使用

通常重试的文件不需要全部类型,仅仅需要重试 js 文件即可

Retry-chunk-load 方案