作者 | 王智勇
前 言
华润数科城市与公共事业部门下属项目组近期完成了一个地产行业遗留复杂业务系统的微服务化改造,目前项目已经成功上线,系统切换过程中实现了原单体系统在线业务数据分批无缝无损迁移到微服务架构新系统,确保了业务平滑过渡。本文分享我们在此次数据迁移过程中的思考、探索和实践总结,希望能够为有类似需求的朋友们提供一些经验借鉴。
背 景
我们本次改造的单体系统承载着核心业务,采用商业开源框架构建,已经运行了 8 年。在这个过程中随着业务发展,开发团队在响应新需求时不断堆砌代码,从而带来了难以避免的代码腐化,加之团队人员大量流动,导致系统的架构、技术文档缺失,最终形成代码逻辑复杂难理解、系统稳定性差,运行效率低,功能扩展困难的局面。
团队曾经尝试采用绞杀者模式将一些非核心业务进行微服务重构,以及采用修缮者模式编写微服务实现一部分新业务需求。然而由于遗留代码臃肿、逻辑实现耦合、功能模块无法复用且缺少测试等多种原因,上述方法针对核心业务代码均不易使用,而且遗留系统在应对日益增加的业务量时已经不堪重负,整体改造迫在眉睫,同时考虑到团队具备丰富的业务和产品专家能够支持总体系统规划,我们最终决定使用系统整体演进的方式对其进行改造。在业务产品专家、架构师和技术负责人设计出服务总体规划蓝图,团队完成微服务拆分和开发、验证后,为保证业务平滑过渡和客户体验,新服务上线投入使用前需要设计完善的数据迁移方案,保证数据的正确性、完整性,下文详细阐述数据迁移方案的设计实施过程。
过 程
下图展示了本次数据迁移过程包含的主要环节:
方案制定环节需要充分考虑到后续环节将会面临的问题挑战,并根据实际需要完成技术选型
脚本编写阶段,重点关注业务数据映射关系,必须与业务和产品团队充分沟通,另外也要关注脚本运行效率,及时优化相关代码逻辑
迁移脚本需要进行完善的测试,因此在测试环境准备环节要尽量使测试数据与生产数据保持一致,同时做好敏感数据管理
迁移脚本在测试环境上运行后,不仅开发人员要去验证数据转换映射的正确性,还需要业务产品团队验证业务流程是否通畅
迁移执行时间窗口有限,需制定并确保技术团队掌握非预期事件应对预案
灰度发布后,需要做好新系统的监控并排查解决线上运行出现的问题
问题挑战
结合遗留系统、业务、团队的现状,我们梳理出本次数据迁移任务过程中主要面临的挑战有以下三个方面:
业务
我们要迁移的线上生产数据包含关键业务如合同、账单、付款信息等,这些数据的容错性极低,也就要求方案必须具备极高的数据迁移准确性
微服务改造过程中,业务专家对业务规则和业务数据进行了重新建模优化,因此数据迁移不仅仅是简单的数据库 - 表 - 字段映射的过程,还需要对数据进行复杂的转换、清洗、补全等操作
出于业务连续性考虑,需要在大约 3 小时的停机时间内,完成数据迁移以及验证工作,经测算实际留给数据迁移执行的时间只有 1 小时,因此我们的方案必须具有较高的执行效率、具备可靠的回滚及重试机制,并在上线前完成充分的模拟验证
为了尽可能减小上线风险,业务提出本次上线新系统需要分成多个批次推广到业务网点,缩小潜在问题影响到的用户数量,这要求方案支持灰度发布、增量迁移
团队
遗留系统根据 DDD 原则拆分为多个微服务,由分布在不同城市的多个团队并行开发,为保证数据迁移工作在多个团队间协调进行,迁移方案需要支持便捷的模块隔离、版本管理、历史记录比对等跨团队协作特性
技术
数据量大:遗留系统数据库包含近千张表,经分析涉及迁移工作的表占近 40%,包含数千万条待迁移数据,需要经清洗转换后写入改造后的几百张表中
配置管理:迁移过程中需要将遗留系统数据导出后根据业务需求跨数据库传输后分别写入到拆分后的多个微服务的库中,迁移方案需要支持方便的配置管理,从而方便模拟测试验证;并且支持按业务要求的分批次上线配置
数据转换复杂:如上文业务部分所述,由于业务规则和业务数据经过重新建模优化,存在大量必须进行多表联合查询才能取到目标数据的场景,也存在无法使用 SQL 实现的数据转换需求,例如生成分布式 ID、敏感数据处理(哈希 / 加解密)、复杂数据字典映射等
方案选型
选型评价方法
为应对以上挑战,我们认为迁移方案应该具备以下特点:
提供快速开发模板或 DSL,减少工作量,降低出错机率
运行高效
支持使用复杂 SQL 查询进行数据清洗、转换、补全
技术栈匹配团队技能,使用 Java/SQL 开发,迁移框架开发调试友好
支持编写定制化代码,复用生成分布式 ID、敏感数据处理(哈希 / 加解密)、复杂数据字典映射等业务代码逻辑
支持 git 版本控制,便于数据迁移脚本的版本管理、回退等
支持跨数据库数据传输
支持 k8s 集群内部署,支持方便的环境配置管理
而通用 ETL 工具的图形界面、API 等特性的重要程度相对不高。
备选方案对比
经考察,团队有相关经验的开源 / 定制化数据迁移框架 / 工具包括以下几种:
Kettle:流行开源 ETL 工具,提供 GUI
Informatica:商业 BI 工具,具备 ETL 功能
DataX:开源 ETL 工具,架构简单,可扩展性较强
Java/SpringBatch:Java 生态提供的批量任务处理框架,可用于数据迁移
SQL/ 存储过程:通过编写 SQL 或存储过程实现数据迁移
以下是对比表格:
可见开源的 DataX 最符合我们的方案评价,成为团队最终选定使用的基础框架。
方案实施细节
上面讲到我们最终选用 DataX 作为本次数据迁移的基础框架,DataX 定义了 job 作为数据迁移脚本的配置模型,一个 job 即对应一个迁移写入目标表,job 配置以 json 编写,支持丰富的限速、并发、容错设置,并且在 job 中可以引用使用 Java 开发的自定义 transformer 对数据进行复杂操作,为迁移脚本开发者提供了强大的灵活性。DataX 内部运行机制可以参考官方文档,从脚本开发者的角度看来,job 运行基本流程大致如下所示:
得益于 DataX 源代码全部开放,结合项目实际,我们从以下几个角度对框架进行了定制化改造:
适配开发部署需要
DataX 默认使用 Python 脚本作为程序启动入口,且启动时需手动指定数据迁移任务配置文件,为降低开发人员上手复杂度,简化操作流程,我们编写了定制化的 Java 启动器,去除了对 Python 的依赖,并添加了自动发现配置文件功能,开发人员只需要提交配置文件,新添加的迁移任务就会在下次 CI/CD 过程中自动被执行
为了支持读写常见的数据源,DataX 官方提供的完整二进制包包含了所有插件,总大小超过 1.6GB,由于本项目采用 k8s 容器化部署,过大的二进制包对编译打包、镜像仓库、容器集群均不友好,我们通过修改 DataX 的扩展加载方式,去除不需要的插件,仅保留 MySQL、CSV 等几个必要的插件,将包大小降低到不超过 20MB
为了支持插件动态加载,DataX 采用 Assembly 打包方式,各模块之间分别编译打包,并使用文件目录管理模块间的依赖关系,这种做法导致团队使用 IDE 开发定制化数据转换逻辑时,必须对 IDE 进行繁琐的依赖路径配置后才能调试代码,带来很多不便,我们将打包方式修改为 Shade Jar,使框架代码可以一键导入 IDE 中进行开发调试,降低开发人员上手难度,大幅提升自定义 transformer 的编写效率
适配验证测试需要
上文提到数据迁移的执行时间窗口需要控制在两小时内,因此在迁移脚本的测试与验证过程中需要汇总统计每个脚本的执行时间、数据传输速率等相关数据,以便于提前发现运行缓慢脚本并做相应的优化,为此我们对 DataX 的性能数据和日志输出模块进行了定制化,每次运行结束后汇总打印相关数据
DataX 数据迁移脚本采用 json 格式编写,数据库连接相关的信息如主机地址、端口、数据库名、用户名 / 密码等都需要明文写在数据迁移脚本中,这种做法在项目场景下存在两个问题:
由于数据迁移的源数据库和目标数据库均为生产库,若硬编码在数据迁移脚本中提交至 git 仓库,存在敏感配置数据泄露的风险。
数据迁移脚本验证测试过程中使用测试库,如果测试时硬编码测试库信息到数据迁移脚本,那么在生产环境下正式运行时,需要手动再改成生产库配置,相关修改繁琐易出错。
为了解决这两个问题,我们定制修改了 DataX 解析数据迁移脚本的逻辑,使得数据库连接相关信息可以通过环境变量传入,这样运行时只需要在 k8s 集群上使用 Secret 配置环境变量即可,兼顾配置管理的安全性与便捷性。
业务需要
前文讲到由于业务规则和业务数据经过重新建模优化,存在无法使用 SQL 实现的数据转换需求,例如生成分布式 ID、敏感数据处理(哈希 / 加解密)、复杂数据字典映射等,这部分我们通过编写数据转换器实现,在转换器中使用与业务逻辑相同的第三方库和代码逻辑,确保转换后的数据与新系统业务代码的兼容性
灰度发布:为响应业务部门提出的分批次上线降低系统切换风险的需求,我们对待迁移数据进行了分析,并将数据分为三类:
基础数据,如城市城区数据、商圈数据等,这部分数据量少,关系简单,可以采用全部迁移的方式,即使上线后需要修正,所需要的人工成本也可以接受。
网点数据,即各网点的业务资源、合同、账单等数据,这部分数据可以使用网点进行切分,当选定一批网点上线后,在数据迁移脚本中过滤出对应数据进行迁移即可。
C 端用户数据,这部分数据量大,且数据间存在较复杂的关系,而且由于 C 端用户与网点间并没有很强的关联关系,无法使用网点信息进行切分,我们最终决定采用按时间进行切分的方式:第一次迁移时迁移全部数据,第二次迁移时迁移自第一次后新增的数据,以此类推。
为方便进行网点和时间过滤,我们对 DataX 的 SQL 查询解析逻辑进行了定制,支持通过编写类似 #{store_id IN STORE_IDS} 或 #{creation_date AFTER MIGRATION_DATE} 的宏来添加过滤逻辑,过滤条件通过环境变量传入。
在完成上述定制化后,新系统在第一批灰度发布网点成功上线,迁移涉及到的约 300 万条数据的读取、转换、写入耗时不到 20 分钟,上线后经业务、产品、网点现场工作人员验证,无严重问题,保证了业务的平滑过渡。
其他实践中的关注点
上文详述了我们在本次数据迁移中结合技术和业务实际对数据迁移框架的选型与定制化,实际上在这些工作之外,还有很多需要关注的点:
测试
测试的重要性无论如何强调都不为过,尤其是对于攸关业务正确性和连续性的数据迁移工作。在本项目中,我们为数据迁移搭建了专用的数据库、微服务集群和配套中间件,并将生产数据经脱敏后导入迁移测试环境数据库中,从而最大化模拟线上环境,尽早发现迁移脚本问题,确保迁移后的数据与业务逻辑相匹配,另外迁移后的数据与系统依赖的第三方 SaaS 服务之间的兼容性也需要完整测试。
规避数据冲突
在灰度发布的场景下,没有完成全部网点上线前,新老系统会并行运行一段时间,数据迁移过程中需要识别哪些数据在并行运行期间可能在两套系统上产生数据冲突,导致增量数据迁移时出现问题,并制定方案加以规避。
潜在数据冲突产生的一个重要源头是自动生成的数据主键,举个例子:假设在老系统上,合同编号使用数据库中的自增 id 实现,并且数据迁移执行时,老系统的合同编号值为 10000,若不做任何处理将合同数据平移至新系统且继续采用自增 id 作为合同编号,则数据迁移后新系统的合同编号也为 10000,这样在新老系统并行运行期间,两个系统新产生的合同编号会产生冲突:未进行灰度发布的网点 O 新签合同会使用编号 10001,已经进行了灰度发布的网点 N 新签合同也会使用编号 10001,当网点 O 也切换到新系统时,会发现由于合同 id 冲突而无法迁入新系统的问题。对于这种场景,我们采用了手工设置自增 id 的修复方式,将新系统的自增 id 设为老系统 max(id)*2,本案例中即将新系统的合同编号设为 20000,从而规避新旧系统并行运行期间生成重复数据 id 导致的冲突。
做好失败应对预案
无论计划多么详尽周密,实际执行时都有可能出现意外情况,数据迁移工作也不例外,因此必须提前规划好应对方案。本次数据迁移工作,我们从两方面做了预案:
遗留系统数据库:通过为迁移脚本新建一个只读用户的方式,确保迁移脚本运行过程中不会修改遗留系统数据库,这样如果发生限定时间内无法完成数据迁移的情况,我们可以回退到原状态,即仍然由老系统为网点提供服务
新系统数据库:上文提到,在本次上线前,团队已经使用修缮者模式和绞杀者模式开发部署了一些微服务,这些微服务的数据库也在本次数据迁移工作的写入目标之内,为保证数据迁移失败时不影响这些微服务,我们在迁移前联系 DBA,确认数据备份机制能够支持回滚,并在数据迁移执行时请 DBA 现场值班以应对突发情况
总 结
随着业务发展,传统的单体架构在响应需求变化方面表现欠佳,随着单一系统的代码量增长,系统复杂度很容易指数级增长,叠加技术债堆积、团队人员流动,导致新特性开发变得越来越困难。为此很多组织转向微服务架构寻求解决方案,微服务架构的灵活扩展、部署弹性、敏捷适配和组件自主演进等特性精准解决了上述痛点。然而在微服务拆分改造具体实施时,保证业务稳定是一个值得关注的话题,本文介绍了我们在新旧系统切换过程中,如何设计数据迁移方案解决相关问题挑战从而达成这一目标,希望能够为有类似需求的读者朋友带来一些帮助。
作者简介
王智勇,现任华润数科企业数字化中心城市与公共架构总监,拥有多年软件开发行业经验,主要服务于大型客户的定制软件开发项目,专注于系统架构设计,团队能力构建,带领团队开发高质量的产品,为客户交付业务价值。在项目需求分析、技术选型、系统架构设计、持续部署、团队能力提升、开发计划制定和系统构建实施等方面有深入的理解和丰富的实践经验,服务客户包括大型国企,国内头部互联网企业,海外政府项目和海外大型 IT 组织,涉及领域包括金融、物流、地产、教育等。
4000520066 欢迎批评指正
All Rights Reserved 新浪公司 版权所有