微前端实战中的一些疑难问题

502 阅读5分钟

本文正在参加「金石计划」

背景

微前端项目也在生产环境运行一年多了,这期间也是遇到一些问题,在此共享出来,大家共勉。如有更好的方案请评论区讨论或者私聊告诉我,万分感谢!

问题及解决方案

依赖循环问题

事故: 该问题其实是我们一直知道的一个隐患,只是盲目的相信了我们其他团队服务的稳定性了和健壮性。而没有提前就部署规避该问题。最后某一日因为业务的一个不合法 ingress 规则导致测试环境崩了,半天没有恢复。

之前容器平台的前后端和业务没有很好地隔离,后端托管在 k8s-core(k8s-test)集群,与业务使用的是同一个网关入口。

前端部署情况:

  • 微前端主应用部署在 k8s容器 上(自依赖)
  • 容器平台前端部署在对象存储 oss,对象存储又部署在 k8s容器 上(循环依赖)

改造: 前后端与业务解耦,其中前端相关静态资源放到阿里云 OSS 上

前端迁移方案:

  • 首先,整个大平台体系采用微前端架构,是一个整体,最初想整体大平台体系整体迁移(流量全部从网关直接转走),即:微前端主应用和所有子应用都迁到阿里云,通过网关将整个大平台体系前端的流量都转到阿里云。

    • 接着,我们梳理了链路,发现有很多未知流量,最初的方案会影响到业务,改动成本高
    • 因为历史问题,容器主应用的流量是没有标识的,很难从 www.xxx.com 这个域名下理清哪些是属于前端的请求(这个域名是收敛域名,下面对应几十个 upstream),网关不知道要转发哪些流量。

image.png

  • 后来,考虑将容器前端脱离微前端主应用,网关只转发容器相关的流量(/k8s-fe),但是会失去整个大平台体系的公共功能,容器维护成本高。

  • 最终,想到了一种折中的方案,我们目的是解耦容器,容器平台的前端是有标识(/k8s-fe)的。我们将主应用也部署阿里云一份,网关转发容器相关的流量(/k8s-fe)到阿里云的主应用。

image.png 上图图意:

  1. 将微前端主应用在容器和阿里云都部署了一份,容器平台前端资源部署在阿里云。
  2. 访问容器平台的链路如图中蓝色箭头所示,通过网关匹配到 k8s-fe 路由将流量转发到阿里云主应用,主应用再请求阿里云的容器平台资源。
  3. 其他平台访问链路不变,如图红色箭头所示,通过网关将流量转发到容器的主应用,主应用拉取对象存储上各产品的资源。
  4. 主应用和所有子应用都在容器平台发布更新版本,主应用发布时会同时上传一份资源到阿里云,容器平台资源会上传阿里云,其他产品资源上传对象存储。

子应用和主应用联调问题

场景

由于有十几个子项目,都是不同的小伙伴在维护及开发,而微前端主应用是我在维护,那么每个子应用如果需要和主应用有联动,基本上都不能在本地调试了。

支持单个及多个子应用使用主应用的测试环境或提测环境在本地联调,方便小伙伴不启动主应用,就可以在本地联调使用到主应用的能力。

所以对主应用改造如下

if (isTice || isTest) {
  // document.cookie = fe_debug_url=http://localhost:${PORT_MAP[app]}/ 可访问本地开发环境子应用
  let MicroApps = Cookies.get('fe_debug_app') || ''
  let MicroUrls = Cookies.get('fe_debug_url') || ''
  MicroUrls = MicroUrls.split(',')
  const getArrayIndex = (arr, item) => {
    var i = arr.length
    while (i--) {
      if (arr[i] === item) {
        return i
      }
    }
    return -1
  }
  if (MicroApps) {
    MicroApps = MicroApps.split(',')
    return MicroApps.includes(app) ? `${MicroUrls[getArrayIndex(MicroApps, app)]}` : `${ORIGIN_URL}`
  } else {
    return MicroUrls[0] ? `${MicroUrls[0]}` : `${ORIGIN_URL}`
  }
  // 'https://tice.xxxx.com/子项目收敛域名'
} 

使用方式:

如果是单个项目联调还可以按照以下方式:

在控制台 或者 代码里边临时 加入:document.cookie = fe_debug_url=http://localhost:8000/

如果多个,需要在 cookies 里边放两个参数,一一对应。举例如下:

fe_debug_url:http://localhost:8000/,http://localhost:8900/
fe_debug_app:logcenter-fe,message-fe

image.png

浏览器缓存问题

场景:

在我们每次变更上线的时候,几乎总是要浏览器清缓存才能让用户看到最新版本。但是让用户清浏览器缓存,有违人道!不可取!🙅‍♂️

前提条件:

我们的前端项目资源都是静态资源,放在静态服务器上。都是以 index.html 为项目入口文件。

那么我们其实发版时候只需要通知 index.html 文件变动就好,浏览器就会重新更新 index.html 文件,则里边的各种打包文件也就自动更新了。

方案:

方案一:利用浏览器缓存策略,增加版本号配置。

增加一个配置,每次发版完之后,手动去更新一下版本,或者能自动更新最好,把这个版本号以接口的形式发送给微前端主应用,拼接到 index.html 后边,这样版本号变了,资源自动就更新了!

方案一的缺点是,加载之前需要请求一个配置接口,等接口成功或者失败之后才能去拼接链接访问 index.html,可能刷新时候有一点延迟性,但是准确性高,保证是真正需要更新的时候才去更新页面。

方案二:利用浏览器缓存策略,部署 nginx 代理配置

在服务端 nginx 中添加禁止缓存的 Header 配置。切记,只对 html 设置禁止缓存,同时不允许 CDN 等对其缓存,配置如下:

location ~ ^/static/(admin1|admin2|admin3)/ { 
    if ($request_filename ~* .*.(?:htm|html)$) {
        add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
    }
}

在上面的配置中,使用了以下响应头:

  • private 只能被终端用户的浏览器缓存,不允许 CDN 等对其缓存。这个会影响到 CDN 缓存命中率,但在代码量较小的情况下,可以禁用。
  • no-cache 需要进行协商缓存,发送请求到服务器确认是否使用缓存。或者可以使用 max-age=0
  • no-store 完全禁用缓存。
  • must-revalidateno-cache 类似,强制到服务端验证,适用于一些特殊场景,例如发送了校验请求,但发送失败了之类。
  • proxy-revalidate 与上面类似,要求代理缓存服务验证有效性。

添加成功后,刷新浏览器效果如下:

image.png

到此为止,已经成功地解决了项目中的浏览器缓存优化问题。

需要注意的是,配置完成后,页面第一次刷新后还是旧代码,这是正常情况(因为浏览器仍在使用之前的缓存),清除缓存然后重新刷新即可。

按照上面配置,多次测试发现可行,目前为止没有发现问题。如果大家觉得还不稳妥,还可以通过添加 ExpiresCache-Control 等响应头,控制浏览器缓存。配置完成后的效果如下:

image.png

方案二的缺点是无脑的在刷 index.html, 每次刷新浏览器都会去请求更新 index.html,索性我们前端项目的 index.html 文件大小都很小,影响不大。如果你的项目 index.html 文件大,不建议使用该方案。

小结

微前端开发及维护中,多多少少都会有点疑难问题,以上就是个人遇到的几个有代表性意义的问题。大家还有其他问题,也可评论区发出来学习和讨论。

以前相关文章

微前端实战 juejin.cn/post/707348…

微前端 qiankun 的应用与实践 juejin.cn/post/709903…

微前端——qiankun 主要特点 juejin.cn/post/716879…