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

一、单体已死,单体永存


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

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

二、拆掉金字塔

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

历史的经验告诉我们人多力量大,既然对方很强大,那就先找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就是使用处的短签名。后续自动引用修复时会用到。

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

相关推荐
wn53120 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
希冀12344 分钟前
【操作系统】1.2操作系统的发展与分类
后端
GoppViper1 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
爱上语文2 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people2 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
罗政8 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
拾光师9 小时前
spring获取当前request
java·后端·spring
Java小白笔记11 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis
JOJO___12 小时前
Spring IoC 配置类 总结
java·后端·spring·java-ee
白总Server13 小时前
MySQL在大数据场景应用
大数据·开发语言·数据库·后端·mysql·golang·php