如何实现一套真正的【去中心化】的微前端【分布式】部署架构
微前端部署的困境
传统部署方案
开发项目微前端项目时通常是一个主应用, 多个子应用, 子应用一般都能支持独立访问, 主应用会作为一个汇总的入口入下图所示。
一般情况下, 我们需要提前在 main 应用配置好 多个子应用的 entry 地址。 类似于这样:
const microConfig = [
{ name: 'sub-app-1', entry: 'http://localhost:5001/remoteEntry.js' },
{ name: 'sub-app-2', entry: 'http://localhost:5002/remoteEntry.js' }
]
这个时候就会有几个问题:
- 添加或者删掉一个 子应用时, 必须要重新配置一下 main 应用的子应用索引的配置,配置起来也比较麻烦。
- 一般都是访问 main 应用作为入口,也只暴露出main 给外部使用。如果 main 应用挂了, 差不多整个系统也就 gg 了。
- 给外网暴露的网站一般需要统一域名, 这样能节省【端口】或【域名】资源, 输入可以通过 nginx 代理配置实现,但是更新过于麻烦。
但是我不想每次修改增加 子模块的时候 还需要重新配置主应用,修改主应用然后重新部署这种没有技巧重复任务就应该是自动化能处理好的事情。我希望我部署了子模块之后, 主应用能自动更新配置。
我们有没有自动生成 microConfig
配置的方法,这个时候就需要一种能自动注册更新的方法。
注册中心方案
我们可以参考后端 sprint-cloud 微服务的方案 ,写一个 类似与 eureka 注册中心的东西。
我们设计一个方案具体如下:
- eureka 可以用 redis 的 发布订阅模式实现
- sub-app-1 子应用运行 nginx 时 后台多运行 一个 nodejs 进程 ,上线下线时向 redis 发布消息,消息中携带上子应用的ip地址信息。
- main 子应用运行是 nginx 时,后台多运行一个进程,从 nginx 监听 子应用上线下线通知 ,更新自己 html 文件夹下的 microConfig.json 文件
这样虽然能够实现自动注册的需求,但是这种方式也有一些缺点。
- 部署成本高,我只是部署一个前端, 还需要 搭建nginx 环境
- 容错率低,如果redis 挂了 或者 main 应用挂了, 前端访问不了。
- 访问出口不能统一,因为浏览器或加重多个 nginx 下的问题,而且每个子应用都需要占用一个【端口】或【域名】资源。
我只是想方便便捷的部署一个前端而已, 结果还要搞 redis ,有必要这么麻烦吗?
分布式部署方案
接着我们继续想办法优化一下这个部署方案。 我想实现一个正在的分布式部署方案, 需求如下:
- 我希望没有主应用和子应用之分, 每个应用都是独立的, 但是我访问任何其中一个应用,都能获取到其他应用的页面。
- 不希望有 redis 或者 mysql 这种额外的服务, 徒增运维和安装成本。
- 访问任意一个应用访问其他应用的页面, 浏览器加载的资源都是可以通过一个【端口】或【域名】,也就是 应用能够自动生成 nginx 代理配置。
- 子应用上下线之后, 其他子应用能够马上知道并更新自己的 microConfig 配置。
- 即便是某个应用被删掉了,不会影响整个系统 运转。顶多就是当前应用的页面 404。
假如我们现在有 3个 微前端模块部署到 3 个 nginx docker 容器
- app1 IP: 192.168.0.1
- app2 IP: 192.168.0.2
- app3 IP: 192.168.0.3
上线流程
- app1 上线, app1 上线之后, 先通过广播说“我上线了, 我的ip是多少”,然后通过广播询问“当前网络空间下,有哪些微前端应用在线”。没人回答,这时 app1 什么也不做
- app2 上线, app2 上线之后, 先通过广播说“我上线了, 我的ip是多少”,
- app1 听到有人上线了, 马上把这个ip记录下来,让后通过ip 访问 app2 的 info 接口,把 app2 信息保存 app1 microConfig.json 下面。
- 接着 app2 再次通过广播询问:“当前网络空间下,有哪些微前端应用在线”,
- 这个时候 app1 有听到了有人问谁在线, app1 说:“我上线了, 我的ip是多少”
- app2 也听到有人上线了, 马上把 app1 这个ip记录下来,让后通过ip 访问 app1 的 info 接口,把 app1 信息保存到 app2 microConfig.json 下面。
- app3 上线,以此类推 。。。
那么怎么实现这个方案呢。
如何实现上面说的 广播询问呢?
其实我们可以通过 multicast-dns
的 respond
api 实现这个广播的功能。
respond
api使用方式如下:
广播消息
const mdns = require('multicast-dns')();
mdns.respond({
answers: [
{
name: `${S_NAMESPACE}-offline`,
type: 'TXT',
data: '3.4.5.6'
}]
});
监听广播
const mdns = require('multicast-dns')();
mdns.on('response', function (response) {
// console.log('got a response packet:', response.answers)
response.answers.forEach((ans) => {
if (!ans.name.startsWith(`${S_NAMESPACE}-`)) {
return;
}
// todo save microConfig.json
})
}
最终实现效果
代码仓库:github.com/yunqiangwu/… 测试方法:
git clone https://github.com/yunqiangwu/distributed-nginx.git
cd distributed-nginx
yarn run test
会直接启动 3 个 nginx 的 docker 容器,
nginx1 的目录结构
nginx2 的目录结构
nginx3的内容
测试部署的内容:
- 获取微前端配置信息
- 从第一个微前端模块访问第二个微前端模块的页面
- 从第一个微前端模块访问第二个微前端主模块的页面
- 从第一个微前端模块访问第三个未暴露访问端口的微前端主模块的页面