背景问题
问题现场:
前端业务系统跳转到新页面失败,控制台报错无法找到新页面的业务代码文件
问题分析:
前端侧
:出于网络性能方面考虑,前端系统不会一次性加载所有页面的业务代码文件,只会加载当前页面需要的资源文件,在跳转新页面时动态加载新页面相应的代码文件
部署侧
:在微服务容器化的部署架构中,由于业务容器无状态,所以每次部署前端容器时 所有的前端文件都是最新构建打包的
问题结论:
根据上述分析,存在这样一类场景,用户保持在 A 页面浏览,A 页面保存 B 页面的业务代码地址,但是没有执行请求。此时,服务器部署了新的前端业务容器,容器中的代码文件全部被刷新。如果用户在新的前端容器部署之后跳转 B 页面,原先 B 页面的业务代码已经不存在于新容器中,导致问题现场复现。
方案调研
问题产生的根本原因在于,前端和静态服务器对于同一文件的生命周期管理存在不一致性,导致在某类特定场景下出现文件周期冲突。解决方案的根本原则是在前后端对齐资源文件的生命周期,基于此根本原则,提出多版本资源文件管理的解决方案,即前端容器中不只管理最新部署的前端资源文件,而是保存备份周期内(目前是一周)的所有资源文件。要完成此技术方案,需要梳理厘清整体前端部署流程,如下图:
技术术语
● vite
:前端构建系统,用于将业务代码编译打包为浏览器可执行的文件。编译打包是一项系统性工作,包含脚手架指令解析,配置文件读取、合并,访问路由,编译文件系统管理等等
● rollup
:前端构建工具,相较于 vite 的功能多样性,rollup 只专注于业务文件的转换、解析和编译
● 增量构建
:对于业务复杂系统,编译是一项计算密集型工作,为了提高编译效率,rollup 每次构建都只会编译改动文件(即增量开发)的部分,除了第一次构建之外
● 幽灵资源
:已经没有被任何版本引用的资源文件
技术分析
经过分析前端部署流程,发现一共有两个阶段有可能进行改造,分别是编译时和运行时
-
编译时
:vite 在构建时,底层实际是调用 rollup 进行文件编译,虽然 rollup 执行了增量构建,但是 vite 在构建之前都会将原先的编译文件整体删除,并且没有预留配置或者插件干预这一行为,除非进行侵入式的修改,但是这对于团队协作十分不便 -
运行时
:当启动前端容器时,容器内保存着当前版本的编译文件,可以利用容器挂载的特性将当前版本编译文件保存到本地文件系统或者远程文件系统进行持久化,并进行文件多版本管理
技术方案
根据分析结果,在运行时进行多版本备份是较好的技术解决方案,步骤如下:
-
容器挂载
:k8s 资源清单中声明文件挂载映射目录 -
备份清理
:将备份周期外的文件备份版本删除 -
新建备份
:将本次版本构建的原始资源文件备份到外部文件系统,并添加备份索引 -
备份合并与删除
:将备份期内的所有资源文件进行合并,存储到当前运行版本,并删除幽灵资源 -
备份拷贝
:将当前运行版本拷贝到资源文件目录中,用作静态文件目录作为浏览器响应
备注:备份索引是一个 KV 对象。key 是资源文件名,value 是数组,保存着引用该文件的版本集合
其他方案
除了多版本部署之外,还可以从其他视角来解决文件周期不一致的问题:浏览器拦截资源请求就是另一种思路。
Service Worker
sw 是浏览器提供的一组 api,用于拦截浏览器页面的所有请求,并依据不同业务需求实现不同的功能,资源请求当然也在其中。假如使用 sw 作为资源请求的代理,当请求的响应为 404 时,可以判定服务器已经进行了新的部署。此时只需要通知页面进行刷新,就可以请求到正确的资源文件。