晋升复盘-SqlServer至Mysql数据无感迁移方案设计与落地

in 博文 with 1 comment

https://sockboomdownload.com/ssr-download/clashforandroid.apk

项目背景

.net项目重构至java项目后,业务数据的存储重心也从SqlServer转移到Mysql。随着产品组业务数据量的激增,数据量较大的业务数据以Mysql分片库甚至分库分表的形式存储成为了团队的共识。

现状:SqlServer的个别数据表面临着业务增长带来的数据体量不断增加,如不加以重视,将来势必会影响相关系统的响应速度和整体性能

避免将来日益膨胀的业务数据对SqlServer的存储瓶颈发起挑战,SqlServer单表迁移至Mysql分片表的工作任务提上了日程...

回顾目标

目标1

搭建一套稳定、高可用的SqlServer至Mysql的业务表增量数据同步系统。
实现SqlServer单表数据和Mysql分片表数据完全1:1,最终实现一份数据两份存储的目的。

目标2

对SqlServer存储量较大的单表,在不影响线上系统正常运行的前提下,平滑、无感的将数据迁移至Mysql分片或分库分表存储。

评估方案

目标1解决方案

分成:增量数据同步全量数据同步两部分。

1. 增量数据同步采用SqlServer CDC + Kafka + Kafka Connect的方案。

kafkaconnect扩展流程2.png

以上方案设计的合理性和可行性都是具备的,但在最终的方案落地过程中,也有很多细节的地方需要处理。
比如:
a. 开启CDC会对SqlServer带来额外的性能开支,为了避免这种情况最终采取在订阅库上开启CDC的方式,才能做到对线上无影响。
b. 引入Debezium数据抓取后需要自定义分发策略来保证SQL的执行顺序和Mysql分片的落地策略不受影响。
c. 消费端避免数据更新压力大还得考虑横向扩展,考虑SQL执行顺序以及相关的失败补偿机制等等。

该方案从设计到落地,花了不少时间去做技术预研(http://wiki.17usoft.com/pages/viewpage.action?pageId=72370150)
也踩了不少坑,才有了最终这套成熟的数据增量同步系统。

2. 全量数据同步采用SqlServer -> 导出txt -> 拆分txt -> 导入Mysql的方案。

image
DBA负责导出文件,我负责写读取文件、拆分文件,最后DBA再将拆分完成后的txt文件导入到对应的Mysql分片(全程脚本运行)。

核心点是:导出全量之前,先暂停Consumer的消费,期间让增量数据积压在Topic,等全量导入Mysql完成后再开启消费开始追积压的消息,从而达到SQL执行顺序不变,追完积压消息后,SqlServer和Mysql数据达到1:1完全一致。

目标2解决方案

实现了目标1,有了SqlServer和Mysql完全相同的两份数据,如何让线上的SqlServer表的读写流量平滑、无感的切到Mysql,这就开始需完全从业务代码入手。这里方案采用AOP动态代理的方式对DAO层进行切割:
image

具体实施

上述流程图只是最终形态,但是对于生产环境,代码开发测试完上线后总不能直接一刀切,所以方案确定了还得做以下准备工作:

  1. 如何准确找到线上所有需要迁移表的DAO层所有读写SQL操作,保证不遗漏?
    解决方案:开发Mybatis拦截器对所有SqlServer数据源的DAO层进行拦截,找到有流量的method方法记录下来。由于SqlServer和Mysql之间的SQL语法有差异不能公用,所以还需开发与之对应的Mysql读写方法(BLL代理层就做的这件事情)。

  2. 改造完成后如何保证新的Mysql读查询结果与原SqlServer读查询结果一致?
    解决方案:引入“异步双读”的概念,让原接口读取SqlServer数据的同时异步读取Mysql数据,再将二者结果进行对比,做到对线上“无感验证”。
    image.png

  3. 同2一样,那如果是插入/更新/删除等写SQL操作又怎么验证Mysql改造的正确性?
    解决方案:引入“小流量双写”的概念,让线上的写SQL的10%流量通过双写(即往SqlServer写又往Mysql写),剩余90%流量仍然通过KafkaConnect数据同步系统来同步数据。再借助同步系统的对比工具,全天候读取SqlServer和Mysql的双库数据进行对比,从而实现“小流量双写”的正确性校验。
    image.png

达成结果

目标1

  1. 打开CDC:对迁移表打开CDC功能(DBA配合)
  2. 接入生产端:创建Connector抓取该迁移表(官方支持RestAPI维护创建)
    image.png
  3. 接入消费端:考虑到扩展性,消费端基于策略模式+模板设计模式只需:1.创建迁移表的枚举策略。2.实现类实现模板方法。两步即可完成消费端的接入。
    image.png
    image.png

完成以上两步后开启消费即可实现KafkaConnect数据迁移系统的增量数据同步。

目标2

最终形成了一套完整开发的体系,能协助定位迁移SQL双读结果的内容与耗时对比双写结果对比
只剩SQL改造这部分是无法偷工减料的过程,也是整个迁移需要投入较多工时的步骤,需要迁移者花时间精力去进行代码开发。

反思过程

不足之处

  1. 在目标2的实现过程中,“异步双读”采用了延迟队列对双库数据进行延迟对比,在40多处改造SQL中始终有2处SQL对比不上,定位发现是业务代码查询结果后再次对引用类型的对象赋值,解决方案是可以查询结果后采用深拷贝再进行对象对比,还是自己考虑疏忽了。
  2. 在目标2的实现过程中,“小流量双写”通过天网日志发现有部分insert语句执行失败,每天都有十几例case,失败原因是主键冲突。但查询Mysql库后发现数据并没有丢失,百思不得其解,于是抱着侥幸心理忽略这类case。最终问题暴露在小流量对比后Mysql比SqlServer库少了200多条,但这200多条却没有双写失败的日志,最终花了两天时间才定位到这和之前insert主键冲突有微妙的关系(和冲突的id相差1),那是因为有个业务代码对插入成功后的入参对象又再次进行了赋值再插入,最终日积月累导致Mysql丢失了200多条数据。所以往往出现了小问题那就是有问题!必须引起重视,不得马虎!
  3. 目标1在全量数据同步部分需要开发、DBA、运维三方协助,DBA导出文件,开发拆分文件,但运维才有权限将文件copy到Mysql。DBA的导出导入和开发的文件读取拆分都是有现成脚本,如果能打通权限问题,从导出SqlServer文件 -> 读取拆分文件 -> 文件拷贝到Mysql服务器 -> 导入Mysql节点,理论上全程都是可以通过shell脚本运行的,不仅能避免人工操作失误也能提高全量导入效率。

亮点之处

  1. 基于KafkaConnect抓取SqlServer CDC数据是一套通用性非常强的方案,我们酒旅DBA也在研究这块跟我一起交流经验,他们也准备搭建一套用于日常SqlServer数据的抓取、同步、维护。
  2. 在KafkaConnect技术方案的预研虽然投入的时间成本非常大,特别是在自定义分片策略上花费了很多精力,也克服了很多困难,在官方文档没有明确阐述的情况下,github检出源码,本地环境搭建,debug源码才一步步最终找到解决方法。所以产出也是看得见的,也形成了一套体系,需要的话方便其他团队组或DBA接入。
  3. KafkaConsumer消费端的代码自认为还是写的比较“漂亮”,无论从功能性的明细划分,还是接入成本的简单易上手,或是代码可读性高,各方面都是比较满意的,兼顾实用与美。

总结规律

顿悟

  1. 再详细的博客也经不起时间的推敲,不变的只有官方文档。
  2. 再详细的官方文档也有出错的时候,多看源码在解决问题的过程中也许能收获更多。
  3. 代码的通用性、扩展性要强,模板设计模式不能少。

规律

  1. 往往出现了不起眼的小问题,其实那就是有问题
  2. 对线上要有敬畏之心,补偿机制只能弥补数据,弥补不了急躁的心