当前位置: 首页 > article >正文

微服务合并

有的团队为了节约机器成本、有的团队为了提升研发效率、有的团队为了降低人均服务数

微服务合并,可以从多个角度入手

  1. 代码重构融合:人工拷贝代码、解决冲突,然后分阶段实施迁移重构。
  2. 代码合并打包:将多个代码仓库,拉取后合并打包,构建成一个包部署。对于编译型语言,也称为合并编译
  3. 服务合并部署:将多个服务,解决环境隔离、版本冲突问题后,合并部署到一个进程。
代码重构融合

原始而纯粹,只要落地阶段细致、严谨,遵循服务迁移的基本套路,做好流量diff,不大会出现严重问题。

以合并两个Java应用为例(将A服务合并到B)

步骤事项(重点完成以下事项)
前置梳理1. 代码迁移范围:①无流量、可下线接口/模块;②必迁移模块梳理。
2. 配置迁移范围:①动态配置:KV、Token、密钥等;②静态配置:application/xxx.properties等。
3. 依赖冲突:迁移前将原服务的依赖升级到与目标服务一致;补充原服务单独使用的特殊依赖到目标服务,防止冲突。
代码迁移1. 新增module/package,实现模块隔离。
2. 命名空间冲突:同名配置类、同名bean、同名的动态配置key、同名的task、线程池名。
3. 资源层客户端隔离:合并后的服务,可能需要连接多个mysql/redis/ES集群,对应的datasource或客户端需要分别初始化;例如不同的mapper接口包使用不同的datasource。
流量diff流量入口异步双写,异步执行结果与原服务执行结果预期一致。
流量切换异步执行结果与原服务执行结果稳定一致后,将流量入口调用,迁移到目标服务的新方法上。

**服务合并,不只是代码合并。**影响代码运行结果的还有:动态配置、组件依赖关系(初始化顺序、bean间依赖)、运行环境有效性(事务、代理、异步生效与否)、消息队列延迟消费配置等。

代码迁移,除了解决命名冲突,还要考虑资源层在使用上的隔离。
对于只将多个小服务 代码合并,但DB等资源层不合并的情况:合并后的服务,不同模块独立使用不同的datasource数据源,需要隔离Databource和DatabourceTransactionllanager,并将迁移代码中的@Transactional指定对应新增的事务管理器;

  • 将A服务合并到B的场景举例,B原本使用的Databource和DatabourceTransactionllanager都是作为default的,对应业务模块的@Transactional无需指定。服务A的代码迁入B后,使用@Transactional时指定对应新增的事务管理器。
  • 合并后的服务,如需连接多个redis/ES集群,也是显示初始化多个客户端,在不同的业务模块中使用不同的客户端进行资源操作;跟在一个服务里使用多个redis/ES集群方式差不多。

线下验证,可以充分利用原服务已有的测试能力,包括单测、自动化测试等。这是理想情况。
服务合并过程中,除了解决冲突需要修改,原服务逻辑在不大改的情况下,可以线下覆盖核心流程;然后线上流量diff,稳定一致后再切流。

上线准备,需要关注各类鉴权、配置迁移。
迁移原服务的接口鉴权、访问资源的客户端鉴权、定时任务配置等。
除此之外,还要迁移服务治理策略的配置:熔断、限流配置、告警策略等。

流量diff在线上环境进行。理论上,需要覆盖所有的读写接口、以及其他形式的流量入口(定时任务or消费组等)。

  • 读接口,进行实时、异步比对,整体成本较低。
  • 写接口,有一定的成本。
优势劣势
新建一套临时表,异步双向
流量入口处,接收写请求后,先同步调用老方法,变更写入正式表;再异步调用(迁移到目标服务后的)新方法,对流量染色、标记是diff流量。
(迁移到目标服务后的)新方法,使用Mybatis Interceptor依据染色改写表名(追加_temp后缀);将数据写入临时表;最后比对两份数据表的一致性。
无侵入、数据隔离1. 需要新起一套数据表;
2. 新方法除了DB变更外,其他缓存、对外通知MQ,也需根据流量染色,在diff阶段忽略不执行(老方法已完整执行、并对外发了MQ,新方法写入临时表用于比对,不用再发)。
同步执行新流程,比对结果快照
接收写请求后,先调用新方法,再调用老方法;
新方法开启事务执行完,查询事务内变更的数据并保存为快照,不提交&回滚事务;
老方法开启事务执行完,查询事务内变更的数据并与新方法的结果快照进行比对,提交事务。
1. 同步串行执行,耗时高;
2. 回滚事务可能产生告警;
3. 只适合在单个服务内重构时使用,跨服务重构时 除非改造写接口,返回结果快照。

其他形式的流量入口(定时任务or消费组等),需要区分任务类型,对于异步写的场景,也可以采用以上方案diff两套数据源或diff结果快照。

流量diff,理论上需要覆盖所有的读写接口。实际读接口实时、异步比对的成本更低。某些基础元数据的业务场景,仅diff读也行:读不一致说明写有问题,读一致,写肯定一致的情况;无需再diff写。

代码合并打包

以微服务的方式开发,以单体服务的方式运行。

对于编译型语言,也称为合并编译,需要在编译阶段 解决命名冲突、依赖冲突,确定唯一的入口函数等,编译成一个二进制的可执行文件。

而像Java这类半编译半解释型语言,需要借助maven-shade-plugin/maven-assembly-plugin插件,构建的jar文件包含所有依赖,同时支持重命名某些依赖的包。

  • 要避免property文件互相覆盖,可以使用AppendingTransformer来对文件内容追加合并。
  • 支持指定唯一的入口类。

对于已落地服务治理的团队,该方式更复杂的是:A和B合并后,服务注册 要注册为合并后的整体;A和B之前的调用方在服务发现时,获取服务列表也是“合并后整体”的。
两个代码仓库合并打包,原服务的动态配置、限流/告警策略等,是否使用原服务的app标识进行获取、服务注册发现 是否使用目标服务的app标识等问题,核心需要本地化基建的支持。

服务合并部署

对于编译型语言,例如Go plugin支持将Go包编译为共享库(.so)的形式单独发布,主程序可以在运行时动态加载这些编译为动态共享库文件的go plugin。但要求依赖版本、Go编译版本都一致。

而Java,使用类加载隔离、实现多个服务的依赖隔离,可以在同一个JVM实例中部署多个服务。

但由于合并代码后,两个服务会共享一个进程,服务占用的资源、CPU消耗、磁盘占用等均会叠加,所以资源利用率低、磁盘、CPU、内存占用小的服务合并更有优势。

如果多个服务使用同一端口,会发生端口竞争,暴露端口时最好使用随机端口。


http://www.kler.cn/a/568630.html

相关文章:

  • 关于SSM项目的整合
  • 3.jvm的执行流程
  • 搭建基于Agent的金融问答系统
  • 安当防火墙登录安全解决方案:零信任认证+国密证书+动态口令构建全方位身份安全屏障
  • iOS 实现UIButton自动化点击埋点
  • 从人口焦虑到科技破局:新生人口减少不再是难题,未来社会已悄然蜕变
  • Mysql的索引失效
  • 数据库拓展操作
  • Vim 常用快捷键大全:跳转、编辑、查找替换全解析
  • 委托者模式(掌握设计模式的核心之一)
  • 华为手机自助维修的方法
  • Memcached监控本机内存(比redis速度更快)
  • C++编程指南21 - 线程detach后其注意变量的生命周期
  • leetcode第77题组合
  • next.js-学习4
  • 蓝桥杯 6.数学
  • 基于springboot+vue的线上考试系统的设计与实现
  • 在 Ubuntu 下通过 Docker 部署 Caddy 和 PHP-FPM 服务器
  • Java—锁—等待唤醒机制
  • 随机树算法 自动驾驶汽车的路径规划 静态障碍物(Matlab)