1. 引言
数据迁移是企业级DTS(数据传输服务)平台的核心业务场景,涉及数据库备份、恢复、跨库复制等多种操作。然而,迁移失败往往带来严重后果:业务中断、时间成本浪费等。统计表明,大量迁移故障源于执行前的配置问题:源端和目标端权限不足、版本不兼容、网络不通等。为此,我们设计了预检查(Pre-Check)机制,在数据迁移任务执行前,对源端和目标端数据库进行系统化的全面验证,提前发现并解决潜在问题,确保迁移任务的顺利执行。本文主要介绍DTS预检查模块的技术原理与核心设计模式,探讨如何设计一个高效、可扩展的数据迁移预检查系统。
2. 整体架构设计
预检查模块采用标准Spring Boot分层架构,由四层组成:Controller层负责API入口,接收前端预检查请求;Service层承载核心业务逻辑,处理参数构建、任务初始化、结果聚合;检查项执行层实现并行调度与超时控制;数据模型层封装参数与结果对象。系统模块图:
核心设计采用模板方法模式定义检查项标准流程,策略模式处理多数据库差异,Spring依赖注入实现检查项的动态加载与扩展。
3. 核心设计模式
3.1. 模板方法模式:AbstractPreCheck基类的设计
预检查模块的模板方法模式实现包含三个核心层次:接口层定义了检查项的统一规范,抽象基类提供了通用的属性和执行框架,具体实现类则专注于业务逻辑的编写。这种分层设计既保证了代码的复用性,又提供了足够的灵活性以应对不同数据库类型、不同业务场景的检查需求。
3.1.1. 接口层设计
接口层由PreCheck接口承担,它定义了所有检查项必须满足的契约。该接口仅有两个方法签名:第一个是check(PreCheckParam)方法,这是检查项的核心执行逻辑,接收预检查参数并返回检查结果;第二个是getOrder()方法,用于指定检查项的执行顺序,默认返回0。接口的设计极其简洁,这符合接口隔离原则,让实现类可以根据需要自由扩展。
public interface PreCheck {
CheckItemResult check(PreCheckParam param) throws Throwable;
default int getOrder(){
return 0;
}
}
3.1.2. 抽象基类设计
抽象基类AbstractPreCheck是模板方法模式的核心实现,它完成了所有检查项共有的通用逻辑。该基类定义了两个核心属性:preCheckItemTaskExec用于关联数据库中的执行记录,便于后续的结果查询和状态追踪;order属性用于控制检查项的执行顺序。
public abstract class AbstractPreCheck implements PreCheck {
@Getter
@Setter
private PreCheckItemTaskExec preCheckItemTaskExec;
@Setter
private int order;
@Override
public int getOrder(){
return order;
}
}
在实际的检查项体系中,AbstractPreCheck通常不是直接使用的基类,可以根据检查项的类型选择更具体的抽象基类。例如,针对数据源连通性检查有AbstractDBDatasourcePreCheck,针对权限检查有AbstractPermissionPreCheck,针对复制场景有AbstractReplicationPreCheck。这些更具体的基类在AbstractPreCheck的基础上,进一步封装了特定类型检查项的通用逻辑,形成了一个层次分明的继承体系。
3.1.3. 模板模式的好处
模板方法模式在预检查模块中实现了共性逻辑复用、执行流程统一、检查项扩展简化,并在团队协作中支持并行开发互不干扰,同时统一接口便于编写通用测试用例,整体提升代码可维护性与开发效率
3.2. 工厂模式+依赖注入:Spring Bean动态加载检查项
3.2.1. 设计思想
工厂模式与依赖注入的结合是预检查模块实现高度可扩展性的关键所在。传统的工厂模式需要开发者手动管理对象的创建和组装,这往往导致代码与具体实现类紧密耦合。而Spring框架提供的依赖注入机制则完美解决了这一问题,它将对象的创建和生命周期管理交给容器来处理,开发者只需声明依赖关系而无需关心对象如何被创建和组装。
在预检查模块中,每一个具体的检查项都被设计为一个独立的Spring Bean,通过@Component注解自动注册到应用上下文中。当需要执行预检查时,系统会根据配置动态获取对应的检查项实例,然后执行其check()方法。这种设计使得检查项的添加、删除、修改都非常方便,无需修改核心业务代码,只需要在对应的包下添加或修改Java类即可。
3.2.2. 检查项的Bean注册
预检查模块中,大量的检查项类都使用了@Component注解进行Bean注册。以源端数据源检查项为例,其注册方式如下:
@Component
public class SrcDataSourceCheck extends AbstractDBDatasourcePreCheck {
// 检查项实现逻辑
}
通过这种方式,Spring容器会在应用启动时自动扫描并注册这些检查项类。每个检查项都拥有唯一的Bean名称(默认使用类名,首字母小写),这个名称在后续的配置和调用中会被使用。
预检查模块目前约有30+个检查项类使用了@Component注解,涉及MySQL、PostgreSQL、Oracle、SQL Server、MongoDB、Redis等多种数据库和中间件。这种大规模使用依赖注入的方式,发挥了微服务架构中“松耦合、高内聚”的设计原则。
3.2.3. 配置化管理和动态加载
虽然检查项通过@Component注解实现了自动注册,但并非所有注册的检查项都会被执行。预检查模块采用了数据库配置的方式来控制哪些检查项应该被执行。系统维护了一张pre_check_config表,其中记录了每种业务类型对应的检查项列表,包括检查项的Bean名称、执行顺序、标题、描述以及是否可忽略等信息。
// PreCheckService中的关键逻辑
public List<PreCheck> getCheckItems(String type) {
List<PreCheckConfig> configs = preCheckConfigDao.getByType(type);
List<PreCheck> checkItems = new ArrayList<>();
for (PreCheckConfig config : configs) {
PreCheck checkItem = applicationContext.getBean(config.getCheckItem());
checkItems.add(checkItem);
}
return checkItems;
}
通过ApplicationContext.getBean()方法,系统可以根据配置表中定义的Bean名称动态获取对应的检查项实例。这种设计实现了配置与代码的解耦:当需要调整检查项的执行顺序、增删检查项时,只需修改数据库配置而无需重新编译代码。同时,这种设计也支持同一个检查项在不同业务类型中复用的场景,只需在配置表中添加相应的记录即可。
3.2.4. 检查项分组与执行调度
在获取到检查项列表后,系统需要按照执行顺序进行分组调度。PreCheckExecutor承担了这一职责,它首先根据检查项的order属性进行排序,然后将相同order值的检查项归为同一组,同一组的检查项可以并行执行,不同组的检查项按顺序执行。
// 简化后的执行逻辑
Map<Integer, List<PreCheck>> orderGroup = checkItemList.stream()
.collect(Collectors.groupingBy(PreCheck::getOrder));
List<Integer> orderList = orderGroup.keySet().stream().sorted().collect(Collectors.toList());
for (int order : orderList ) {
List<PreCheck> group = orderGroups.get(order);
List<Future<CheckItemResult>> futures = group.stream()
.map(item -> executor.submit(() -> item.check(param)))
.collect(Collectors.toList());
// 等待该组检查项执行完成
for (Future<CheckItemResult> future : futures) {
future.get(timeout, TimeUnit.SECONDS);
}
}
这种分组并行执行的机制既保证了具有依赖关系的检查项按正确顺序执行(例如必须先执行数据源连通性检查,才能进行后续的权限检查),又最大化地提高了执行效率。线程池的配置为16个核心线程、64个最大线程,能够满足高并发场景下的执行需求。
3.2.5. 数据库类型平滑扩展
当需要支持新的数据库类型时,只需在相应的检查方法中添加分支逻辑。以权限检查为例:
if (MYSQL.getType().equals(type)) {
return checkPermissionForMysql(datasource, permissions);
}
if (CLICKHOUSE.getType().equals(type)) {
return checkPermissionForCk(datasource, permissions);
}
// 新增数据库类型只需添加新的分支
3.2.6. 依赖注入的扩展优势
- 检查项易扩展:新增检查项只需实现业务逻辑并配置,无需改动现有代码,符合开闭原则。
- 数据库易适配:新增数据库类型只需新增对应检查逻辑,与原有代码隔离互不影响。
- 业务场景易拓展:支持新业务只需配置新检查项集合与映射关系,无需大规模重构。
- 便于单元测试:检查项由 Spring 容器管理,可轻松 Mock 依赖;低耦合设计支持单检查项独立测试
4. 高可扩展性实现思路总结
预检查模块的可扩展性设计是其核心亮点之一,主要体现在三个维度
系统启动时自动扫描注册,执行时从配置表动态加载,实现检查项的“即插即用”。
3. 配置驱动的动态调整。检查项配置存储在数据库中,运维人员可通过修改配置实时调整检查项行为,无需代码变更和重启服务。这种配置+注解的双驱动设计,使得预检查模块能够快速适应业务变化和需求迭代。






