浅谈数据迁移

1,639 阅读5分钟

在正常的版本迭代过程中,对项目的数据进行迁移的情况还是比较常见的,一般来说,对数据迁移基本上是以下两种情况:

  • 整体迁移
    • 直接将项目的全部数据从一个库迁移到另一个库
  • 部分迁移
    • 将项目中的部分数据迁移到另一个库

整体迁移

顾名思义,就是直接将整个项目的数据进行迁移,一般这种情况,需要迁移的数据,其数据结构都是一致的。(该类型的迁移,不是本次的主要内容,这里粗略的说一下) x3bpUx.png

常见的场景:

  • 在共用的数据库实例中,将某个项目的数据独立出来
  • 机房的数据迁移
  • 切换不同的云厂商
  • ...

需要注意的问题 整体迁移的话,可以分为是否可以停机迁移

  • 停机迁移
    • 这种情况就相对简单一些,由于不存在迁移过程中,数据变更的情况,直接将旧数据同步到新的数据库中即可
  • 不停机迁移
    • 这种情况需要考虑的地方就比较多一些, 一般迁移时,会挑选非业务高峰期操作,新旧数据的同步,新旧数据库是否在同一网络,是否需要保持一段时间新旧数据库同时存在(主要是验证迁移后的数据是否正常)等等 一般情况下,这类型的迁移直接交给DBA处理即可,如果是自己处理也可以使用一些主流的数据库迁移工具,比如:datax、canal、otter、drc、dts、drds等等

部分迁移

随着业务的更新迭代,发展到一定阶段的时候,现有项目的架构设计,有时不能很好的满足未来规划的功能迭代,因此需要对现有架构进行调整、重构,同时也有可能会将一部分的业务功能,进行拆分处理,将一些功能微服务化,这时候也就会涉及到将该功能的数据从原来的数据库中迁移出来,这种类型的数据,数据结构基本上跟原来的是有差异的。

x8y7J1.png

这种迁移一般也会有两种

  • 功能拆分、实现完成后,统一将就旧功能迁移到新的项目里
  • 项目架构变化很大,一些新功能在旧版本不支持,如果用户想使用新功能,就需要升级到新的版本,这就会涉及到用户自主选择升级迁移数据

第一种方式,相对来说会容易处理一些,一般情况下,功能实现后,上线时做一次数据的迁移处理就可以了,这种类型的迁移,可以有以下几种方式处理:

  • 如果数据不是很复杂,直接通过sql将原来的数据导出并重新组装后生成新的sql,更新到新的数据表中
  • 如果数据比较复杂,比如涉及到多表数据的相关信息,也就是说迁移的数据会涉及到业务逻辑处理的,这种情况,可以在项目中讲将这部分的数逻辑处理好,通过一个迁移处理的接口,触发迁移操作。使用这种方式需要注意一点,这个触发迁移的接口是具有幂等性的,可以重复触发处理,不会影响迁移的最终结果

第二种方式,需要有一定的步骤处理,正常来说,对这类的数据迁移,数据肯定是可以以个人或者以组织为维度进行区分的,这类型的数据也可能会涉及到很多个模块。 数据的迁移有几个点是需要特别注意:

  • 各个模块的数据,最好单独处理迁移
  • 迁移过程中,出现异常的模块数据,要记录异常的日志
  • 单个模块迁移出现异常时,不能影响后续模块的迁移操作(有关联的模块除外)
  • 出现异常的模块数据,要有重试的机制
  • 可以重复触发迁移操作,但不能影响迁移的数据正确性
  • 迁移过程中最好能够限制当前的个人、组织的数据更新操作

对于第二种类型的迁移,可以考虑使用模板方法来处理,因为各个模块的数据迁移的流程基本都是一样的,大概的流程如下:

  • 数据格式转换处理:将原始的数据转化成新的数据结构
  • 数据迁移更新处理:将转换后的数据更新到新的数据库中
  • 数据迁移后的处理:当前模块迁移完的后续处理
  • 数据的重试处理:主要处理重试时的逻辑处理

模板代码格式可以参考:

public abstract class AbstractMigrationTemplate<R> {

    /**
     * 数据迁移处理
     *
     * @param orgCode
     * @param module
     * @param isRetry 是否为重试迁移,true=异常重试迁移,false=正常迁移
     */
    public MigrationStatusEnum migrationData(String orgCode, String targetOrgCode, MigrationModuleEnum module, Boolean isRetry)  {
        log.info("开始迁移:{}, {}", orgCode, module);
        List<R> objects = new ArrayList<>();
        if (isRetry) {
            objects = retryConvertData(orgCode, targetOrgCode, module);
        } else {
            objects = convertData(orgCode, targetOrgCode);
        }
        List<List<R>> lists = Lists.partition(objects, MutableProperties.getMigrationBatchNum());
        boolean migrationResult = true;
       try {
            migrationToIntegration(lists);
        } catch (Exception e) {
            log.error("迁移异常:{} {} {}", orgCode, module, e);
            migrationResult = false;
        }
        log.info("当前模块迁移到集成平台结束:{}, {}", orgCode, module);
        afterMigrationData(orgCode, module);

        MigrationStatusEnum status = migrationResult ? MigrationStatusEnum.MIGRATION_SUCCESS : MigrationStatusEnum.MIGRATION_ERROR;
        log.info("当前模块迁移结束:{}, {}", orgCode, module);
        return status;
    }

    /**
     * 数据格式转换处理
     *
     * @return
     */
    protected abstract List<R> convertData(String orgCode, String targetOrgCode);

    /**
     * 重试处理
     *
     * @param orgCode
     * @param module
     * @return
     */
    protected abstract List<R> retryConvertData(String orgCode, String targetOrgCode, MigrationModuleEnum module);

    /**
     * 数据迁移接口调用
     *
     * @param data
     */
    protected abstract void migrationToIntegration(List<R> data) throws Exception;

    /**
     * 数据迁移后
     *
     * @param orgCode
     * @param module
     */
    protected abstract void afterMigrationData(String orgCode, MigrationModuleEnum module);
}

总结

数据迁移是一个比较复杂的操作,正常的操作,往往需要考虑很多边界条件,比如:数据出现异常时的处理、增量数据的同步处理等等。很多时候迁移的数据也需要根据业务需求来处理,比如有些数据是否可以不用迁移等等。总之在保证数据准确的前提下,还需要根据当前业务的现状,调整合适的迁移方案进行处理。