Page 147 - 《软件学报》2020年第11期
P. 147
丁丹 等:场景驱动且自底向上的单体系统微服务拆分方法 3463
力进行业务分析和领域建模.Rademacher 等人 [20] 针对领域驱动设计中出现的细节缺失问题,提出一种模型驱动
的开发方法,通过引入中间模型,明确了微服务接口和部署细节.其中提到的 AjiL [21] 工具可以用于微服务系统的
可视化建模以及中间模型的生成.Levcovitz 等人 [22] 通过人工识别子系统、对数据库表进行分类,从而根据代码
静态依赖图自底向上进行微服务划分.Chen 等人 [23] 分析了业务需求、绘制数据流图,将具有相同输出数据的操
作和对应数据划分为一个微服务.
单体系统的微服务化拆分也可以借鉴传统软件模块化的思路和方法,已有不少这方面的研究.Service
Cutter [24] 是一个用于服务拆分的可视化工具,输入是用户自定义的一系列接口操作和用例,根据操作间的耦合
程度对接口进行聚类划分.但是 Service Cutter 要求用户编写所有输入文件,时间成本很高,并不适用于大型系统
的改造.Abdullah 等人 [25] 则使用系统访问日志和无监督的机器学习方法将系统自动分解为具有相似性能和资
源要求的 URL 组,将每组 URL 映射为一个微服务.这种方法将请求文档的大小和日志中的 URI 请求响应时间
作为机器学习的输入特征,缺点在于只将性能相似度作为度量标准,没有考虑设计上的内聚性或耦合性,拆分结
果不利于服务的修改和扩展.Mazlami 等人 [26] 通过分析代码和变更历史,根据代码的逻辑、语义等方面的关联,
生成类之间的耦合关系图,最后对图进行聚类、得到微服务拆分方案.而 Jin 等人 [27] 认为,程序的很多行为并没
有显式反映在源码中,且代码级别的关联并不完全等同于功能的内聚.他们通过监控系统、动态收集代码的执
行路径,实现了一种面向功能的微服务抽取方法.以上两种方法分别从静态和动态两个角度对系统进行分析,再
寻找恰当的聚类方法对系统进行以类为最小移动单位的拆分.但是传统软件模块化方法并不能实现对单体系
统的数据库拆分,而没有进行数据库拆分的微服务拆分是不彻底的.随着系统的演化和数据量的增长,数据库操
作将成为整个系统的性能瓶颈.在数据拆分之后,一个类的不同方法可能会由于所操作的数据不同而被划分到
不同的微服务中,所以不可避免地会涉及到对类的拆分,这就要求在拆分粒度上达到比类更细化的方法级别.
单体系统的微服务拆分存在不同于单体系统模块化的地方.Taibi 等人 [28] 就指出,从单体到微服务,应该是
去识别可以从单体中隔离出来的独立业务流程,而不仅仅是对不同 Web Service [29] 的特征提取;微服务之间的访
问,包括私有数据和共享库,都必须被小心地分析.前文提到的工作,一部分缺乏对独立业务流程的识别,另一部
分没有将数据作为重要的拆分对象、给予充分的关注.本文在这两点上做了尝试和结合,以现有的代码和数据
模式为出发点,将业务流程、方法调用链和数据表这三者进行关联,使得业务的独立性、代码的内聚耦合性、
数据间的关联度都成为最终的拆分依据.同时,本文所提方法的自动化程度和推荐方案的完整度更高,减少了人
工输入的数据量和人工分析时间.
2 微服务拆分方法
现实中的大部分应用系统都是以数据为中心、围绕数据库进行构建的,因此,系统对于数据的访问方式很
大程度上体现了业务逻辑.本文所提出的微服务拆分方法将数据关联作为微服务拆分的主要依据,通过分析不
同业务场景下的数据访问规律,生成数据拆分方案,进而生成代码模块的拆分方案.方法的输入为一套覆盖了单
体系统绝大部分业务场景的测试用例,每个用例标记一个权重,表示该用例对应业务场景的相对重要程度.方法
的输出为系统的微服务拆分方案,包括每个微服务应该包含的数据表和代码.
方法的整体流程如图 1 所示,可分为“生成数据访问轨迹图”、“生成微服务拆分方案”、“反馈调整”这 3 个
主要部分,整个过程是迭代可控的.
• 第 1 部分由“标记测试用例权重”“在单体系统上配置监控工具”“运行测试用例、收集日志”和“构建数
据访问轨迹图”这 4 个步骤组成,主要是利用已有的监控工具 Kieker 对单体系统在不同业务场景下的
方法调用和数据访问轨迹进行监控和记录.
• 第 2 部分对应图 1 中的第 5 步~第 9 步,即通过分析数据访问轨迹生成数据表之间的关联权重图,利用
聚类算法生成数据表的拆分方案,再根据数据访问轨迹图自底向上进行搜索,生成代码拆分方案.
• 图 1 中表示为虚线的附加步骤代表方法中的第 3 部分,通过人工的反馈对结果做出微调,使得最终的拆
分方案更符合实际需求.