模块拆分之道 -- 基于语义理解自动化拆分实践

一、单体已死,单体永存


在微服务设计之初,我们对业务进行功能划分,用一个个微服务支撑起相应的业务,仿佛焦泥坑中的上古巨兽铮开了桎梏身形的枷锁,呼喊着:"单体已死,微服务起飞",一切似乎都在朝着预期的方向进行。

而随着业务快速迭代,单个微服务项目的体量也在悄然膨胀,渐渐地新功能迭代速度放缓,潜在问题频发,就像千年前的埃及人,扛着巨大的木头工具,推着数吨重的石块,不断搬运累积到未完工的金字塔上,口中呼喊着:"单体永存"。

二、拆掉金字塔

现在"大单体微服务"已成为阻碍完工的主要因素,要做的就是将其拆成多个小的,那我们要如何做呢?

历史的经验告诉我们人多力量大,既然对方很强大,那就先找1000个身强力壮的人来,夜以继日地搬运石块,直到完成目标。

而辩证法又告诉我们,人多真的力量大吗?

阿基米德甚至说给他一个支点就可以撬动整个地球,那我们是否也存在一个支点也可以撬动整个庞然大物呢?

答案显然是有的!

计算机发展的经验告诉我们,重复且简单的工作最终都会被计算机取代,被自动化取代。我们的目标就是实现一套自动化工具,能够自动理解和拆分这个"金字塔"。

三、语义理解就是支点

人工拆分单体项目屡见不鲜,应该说是绝大多数都会选择这样做,因为人工可以去理解拆分规则,执行拆分动作,修复拆分产生的引用问题,同时最重要的是人工可以做到方法级拆分。下面我们就从上述三个点来看看,如何基于语义来达成这个目标。

1. 拆分目标规则

我们以最简单的拆分为例,即将一个单体服务拆分成两个微服务,一个后台服务和一个运行服务。两个服务有不同的API接口,RPC接口。具体如下:

后台服务 前端服务
API接口 /api/bs/order/ /api/order/
RPC接口 UserManageService UserDataService

2. 方法级拆分

根据拆分规则,我们首先可以找到一些入口类文件,然后再根据每个方法调用的堆栈去判定其他方法如何拆分。一个方法应该属于哪里会存在3种情况:

(1)后台服务:该方法被后台入口类文件直接或间接引用,且没有被运行服务方法直接或间接引用。

(2)运行服务:该方法被运行入口类文件直接或间接引用,且没有被后台服务方法直接或间接引用。

(3)公共包:该方法同时被后台接口和运行接口直接或间接引用。

如此来看,我们只要根据每个入口方法,逐层逐一地对方法进行确认,以我们的目标服务来计算,有2400多个类文件,超过40000个方法要识别。

3. 引用修复

当我们执行拆分后,类名会增加前缀进行重命名以此避免编译冲突。如此一来以前引用是UserService,现在部分方法拆分到了公共服务变成了CommonUserService,编译就无法通过,需要取将之前对foo()方法的注入对象由UserService改成CommonUserService。

综合来看,步骤2和步骤3属于重复性高,规则固定的场景,非常适合用自动化实现。

四、如何做项目的语义理解

4.1 项目结构

以springcloud为例,一个常规的maven多模块项目,由以下几个部分组成:

  1. 启动类
  2. properties配置文件
  3. dubbo接口配置文件
  4. pom.xml依赖文件
  5. 子模块
    • java文件
    • mybatis的xml文件
    • pom.xml子模块依赖

其中子模块数量不固定,可能存在多个。

4.2 解析项目

我们使用一些符号来标识项目解析产生的结果。

  1. 对于拆分,我们可以理解为分组,总共有3个分组:bs(后台分组)、runtime(运行分组)、common(公共分组)。
  2. 每个分组内是方法的集合,我们使用m1(后台方法集合)、m2(运行方法集合)和m3(公共方法集合)来标识。
  3. 每个分组内的文件集合:f1(后台文件集合)、f2(运行文件集合)和f3(公共方法集合)。
  4. 全局方法调用栈集合:callStack,本质上是哈希表结构,第一层是入口文件的方法签名,下级是相应的调用堆栈方法签名,层级不固定。
  5. 全局接口和实现类关系:使用哈希表结构,将接口签名和实现类签名对应起来。
  6. 全局子类与继承类关系集合:使用哈希表结构。
  7. 全局Bean类集合:Bean是个很特殊的类,他基本不需要拆分,所以需要单独保存。
  8. 类与方法关系集合:每个类中含有的方法集合,Key是类全路径,Value是方法签名集合。这里需要注意,我们没有区分方法重载,多个重载方法会被认定为同一个签名。
  9. 入口方法集合:使用哈希表来记录,每个分组值作为Key,方法集合作为Value。
  10. 全局静态类集合
  11. 方法使用短签名:方法在代码中的文本形式,比如user.get(id),那么user.get就是使用处的短签名。后续自动引用修复时会用到。

暂时写到这,后面实际拆分放到下一篇中。

相关推荐
2401_8955213420 小时前
SpringBoot Maven快速上手
spring boot·后端·maven
disgare20 小时前
关于 spring 工程中添加 traceID 实践
java·后端·spring
ictI CABL20 小时前
Spring Boot与MyBatis
spring boot·后端·mybatis
小江的记录本1 天前
【Linux】《Linux常用命令汇总表》
linux·运维·服务器·前端·windows·后端·macos
yhole1 天前
springboot三层架构详细讲解
spring boot·后端·架构
香香甜甜的辣椒炒肉1 天前
Spring(1)基本概念+开发的基本步骤
java·后端·spring
白毛大侠1 天前
Go Goroutine 与用户态是进程级
开发语言·后端·golang
ForteScarlet1 天前
从 Kotlin 编译器 API 的变化开始: 2.3.20
android·开发语言·后端·ios·开源·kotlin
大阿明1 天前
SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现
java·spring boot·后端
Binary-Jeff1 天前
Spring 创建 Bean 的关键流程
java·开发语言·前端·spring boot·后端·spring·学习方法