背景
通常,系统环境分为生产、测试和开发等多套,测试又可能因为验证不同的业务版本、BUG 部署 N 套, 意味着每套环境都会有一整套系统,从入口网关到大量的微服务节点,还有数据库等等。人力上需要有人去维护它们,无论是用于测试数还是系统的运维工作,每多一套系统都需要额外部署和购买大量资源,其中很多服务节点的版本是相同的,这都大大增加了成本。
为了解决这样的问题,我们可以通过网关路由的方式,比如测试环境,所有的应用都部署在一套环境中,通过 HTTP Header 信息将不同的应用通过路由串联起来,大概如下:
Server A V1 -> Server B V1
Server A V2 -> Server B 其它版本
前端资源怎么区分多版本
这里的 SSR 服务,基本是移动端的 hybrid 应用,所以在 Native 打开它时可以在 HTTP Header 注入版本信息,如果 PC 端,可以通过其它方式告诉服务器需要路由的版本,比如 URL Query 或 Cookie 之类
- 前置一个网关应用,在收到版本信息后,路由到 SSR 服务
优点:
- 无需改变当前的 SSR 服务
缺点:
- 作为 SSR 服务,其本身已经是一个网关,再前置一个网关有些奇怪
- 每部署一个 SSR 服务节点,可能就需要更新前置网关的配置,需要 2 步操作
- 需要多个 Docker 部署不同版本的 SSR 服务
- 改造 SSR 服务,收到 HTTP Header 版本信息后,在内部做版本的路由
优点:
- 一个 SSR 应用,就一个 Docker
- 只要告诉一个应用(SSR)当前有哪些是需要使用的版本即可
缺点:
- 市面上大部分的 SSR 框架都是对应一个 SSR 版本的,不存在多版本并行,需要一定的开发和维护
综合考虑,既然是为了节省整体成本,那就彻底点,选择了第二个方案,一劳永逸。
改造 SSR 服务
CI\CD 阶段
打包客户端和服务器资源,上传到静态服务器上,其中包含重要的 client-manifest.json 和 server-manifest.json,两者记录的是资源的名称和其对应的资源访问路径,client-manifest.json 用于在服务渲染时候根据资源名插入指定的访问路径,server-manifest.json 用于将资源从静态服务器下载到 Node.js 服务器上,用于 SSR 用。
启动和运行阶段
- 访问配置中心,找到该应用的部署版本,比如 [‘v1,’v2’]
- 与本地版本做 diff 算法,算出新增和遗弃的版本
- 拉去新版本的 SSR 文件到本地磁盘
- 路由解析,因为内部的 SSR 技术被广泛应用,为了不改动代码,通过@babel/parser 和 @babel/traverse 这两个类库,先将代码转成 AST,再解析出路由信息
- 移除本地遗弃的 SSR 文件
- 将新的路由信息更新到路由表中
- 定时执行上述步骤
我们用的是 Egg.js,所以上述步骤在 Agent 中完成,再广播给所有 Worker 进程。另外,为了更好的迭代和任务划分,使用PlugBoard插件化所有步骤。
客户端发起访问,找到对应的路由版本(如果没有走默认路由),路由请求,有缓存直接输出,没有就走 SSR 流程,查找该版本的 client-manifest.json,找到对应的浏览器访问路径,返回给客户端。
至此,整个方案的流程完毕。项目上线后,可以通过关闭多版本共存的参数,按照市面上的常规流程运行应用。如果小团队的运维体系薄弱(没有 K8S 之类运维系统),也能用该套方案实现一定的蓝绿发布等。
Comments