如何将数亿MySQL数据无缝迁移到MongoDB?

2020 年 11 月 17 日

如何将数亿MySQL数据无缝迁移到MongoDB?

在好大夫在线内部,S3系统负责各业务方操作日志的集中存储、查询和管理。目前,该系统日均查询量数千万次,插入量数十万次。随着日志量的不断累积,主表已经达到数十亿,单表占用磁盘空间400G+。S3是业务早期就存在的系统,当时为了简单快速落地,使用了Mysql来存储,随着业务的不断增长,同时也要兼顾性能和可扩展性,到了必须要重新选型的时候了。


新项目命名为:LogStore。

目标

1、安全性


S3 系统在设计之初,没有按业务系统考虑数据隔离,而是直接采用 key(系统 + 类名 + id) + 有限固定字段 + 序列化 value 的方式进行存储,这种方式显然不便于后续集群拆分和管理。LogStore 系统要在逻辑上进行数据区域划分,业务方在接入时要指定 app 进行必要的权限验证,以区分不同业务数据,进而再进行插入和查询操作。

2、通用性


S3 主要提供一种 3 层结构,采用 MySQL 固定字段进行存储,这就不可避免的会造成字段空间的浪费。LogStore 系统需要提供一种通用的日志存储格式,由业务方自行规定字段含义,并且保留一定程度的可查询维度。

3、高性能


S3 系统的 QPS 在 300+,单条数据最大 1KB 左右。LogStore 系统要支持当前 QPS 10 倍以上的写入和读取速度。

4、可审计


要满足内部安全审计的要求,LogStore 系统不提供对数据的更新,只允许数据的插入和查询。

5、易扩展


LogStore 系统以及底层存储要满足可扩展特性,可以在线扩容,满足公司未来 5 年甚至更长时间的日志存储需求,并且要最大化节省磁盘空间。


方案选型


为了达成改造目标,本次调研了四种存储改造方案,各种方案对比如下:



1、我们不合适—分库分表


分库分表主要分为应用层依赖类中间件和代理中间件,无论哪种均需要修改现有 PHP 和 Java 框架,同时对 DBA 管理数据也带来一定的操作困难。为了降低架构复杂度,架构团队否定了引入 DB 中间件的方案,还是要求运维简单、成本低的方案。

2、我们不合适—TiDB


TiDB 也曾一度进入了我们重点调研对象,只是由于目前公司的 DB 生态主要还是在 MGR、MongoDB、MySQL 上,在可预见的需求中,也没有能充分发挥 TiDB 的场景,所以就暂时搁置了。

3、我们不合适—ElasticSearch


ELK-stack 提供的套件确实让 ES 很有吸引力,公司用 ES 集群也有较长时间了。ES 优势在于检索和数据分析领域,也正是因为其检索和分析的功能的强大,无论写入、查询和存储成本都比较高,在日志处理的这个场景下,性价比略低,所以也被 pass 了。

4、适合的选择—MongoDB


业务操作日志读多写少,很适合文档型数据库 MongoDB 的特点。同时,MongoDB 在业界得到了广泛的使用,公司也有很多业务在使用,在 MongoDB 上积累了一定的运维经验,最终决定选择 MongoDB 作为新日志系统存储方案。


性能测试


为了验证 MongoDB 的性能能否达到要求,我们搭建了 MongoDB 集群,机器配置、架构图和测试结果如下:

1、机器配置


MongoDB 集群 3 台机器配置如下:

2、架构图

3、测试场景


本次 MongoDB 测试采用 YCSB(https://github.com/brianfrankcooper/YCSB)性能测试工具,ycsb 的 workloads 目录下保存了 6 种不同的 workload 类型,代表了不同的压测负载类型,本次我们只用到了其中 5 种,具体场景和测试结果如下。


(1) 插入平均文档大小为 5K,数据量为 100 万,并发 100,数据量总共 5.265G 左右,执行的时间以及磁盘压力

结论:插入 100w 数据,总耗时 219s,平均 insert 耗时 21.8ms,吞吐量 4568/s


(2) 测试 90%读,10%更新,并发 100 的场景

结论:总耗时 236s,read 平均耗时 23.6ms, update 平均耗时 23.56ms,吞吐量达到 4225/s


(3) 测试 读多写少,100%读 ,并发 100 场景

结论:总耗时 123s,平均 read 耗时 12.3ms,吞吐量达到 8090/s


(4) 测试读多写少,90%读,10%插入,并发 100 的场景

结论:总耗时 220s,read 平均耗时 21.9ms,insert 平均耗时 21.9ms,吞吐量达到 4541/s


(5) 测试 混合读写,50%读,25%插入、25%更新,并发 100  的场景

结论:总耗时 267s,read 平均耗时 26.7ms,update  平均耗时 26.7ms,insert  平均耗时 26.6ms ,吞吐量 为 3739/s

4、测试结果对比



可以看出 mongodb 适合读多写少的时候,性能最好,读写速率能满足生产需求。

无缝迁移实践

第一步:系统应用层改造+LogStore 系统搭建


首先,在 S3 系统中内置读开关和写开关,可将读写流量分别引入到 LogStore 系统中,而新应用的接入可以直接调用 LogStore 系统,此时结构示意图如下:

第二步:增量数据同步


为了让 S3 系统和 LogStore 系统中新增数据达到一致,在底层数据库采用 Maxwell 订阅 MySQL Binlog 的方式同步到 MongoDB 中,示意图如下:



Maxwell(http://maxwells-daemon.io)实时读取 MySQL 二进制日志 binlog,并生成 JSON 格式的消息,作为生产者发送给 Kafka,Logstore 系统消费 Kafka 中的数据写入到 mongodb 数据库中。


至此,对于业务方现有日志类型,新增数据在底层达到双写目的,S3 系统和 LogStore 系统存储两份数据;如果业务方新增日志类型,则直接调用 LogStore 系统接口即可。接下来,我们将对已有日志类型老数据进行迁移。

第三步:存量数据迁移


此次迁移 S3 老数据采用 php 定时任务脚本(多个)查询数据,将数据投递到 RabbitMQ 队列中,LogStore 系统从 RabbitMQ 队列拉取消息进行消费存储到 MongoDB 中,示意图如下:


(1) 由于原 mysql 表中 id 为 varchar 类型并且非主键索引,只能利用 ctime 索引分批次进行查询,数据密集处进行 chunk 投递到 mq 队列中。


(2) 数据无法一天就迁移完,迁移过程中可能存在中断的情况。脚本采用定时任务每天执行 20h, 在上线时间停止执行,同时将停止时间记录到 Redis 中。


(3) 由于需要迁移数据量较大,在 mq 和消费者能承受的情况下,尽可能多地增加脚本数量,缩短导数据的时间。


(4) 脚本执行期间,观察业务延时情况和 MySQL 监控情况,发现有影响立即进行调整,以保障不影响正常业务。

第四步:校验数据


老数据导入完成后,下面就要对老数据进行校验,校验从两个方面进行: 数据量和数据完整性。


数据量:基于 S3 系统老数据的 id, 查询在 MongoDB 中是否存在,如果不存在则进行补偿重发。


数据完整性:对于 S3 和 MongoDB 中的数据按照相同规则进行 md5 校验,校验不通过则进行补偿重发。


第五步:数据双写


将应用层预制的写开关打开,将流量导入到 LogStore 中,此时 mysql 的流量并没有停掉,继续执行 binlog 同步。结构如下:


从图中可以看到,从 S3 调用点的写接口的流量都写入到 MongoDB 数据库 backuplogs 集合中,为什么不直接写入到 logs 表中呢?留个小悬念,在后文中有解释。

第六步:灰度切换 S3 读到 LogStore 系统

上文我们提到,对于 S3 系统应用层读写调用点均分别内置了切换开关,打开应用层读开关,所有的读操作全部走 LogStore, 切换后示意图如下所示:


第七步:灰度切换写接口到 LogStore 系统

打开应用层写开关,所有写操作会通过 mq 异步写到 MongoDB 中,那如何证明应用层写调用点修改完全了呢?


上文中双写数据一份到 logs 表中,一份到 backuplogs 表中,通过 Maxwell 的 Binlog 同步的数据肯定是最全的,数据量上按理来说 count( logs) >= count(backuplogs), 如果两个集合一段时间内的数据增量相同,则证明写调用点修改完全,可以去掉双写,只保留 LogStore 这条线,反之需要检查修改再次验证。切换写完成后,示意图如下:


MongoDB 与故障演练


故障演练能够检测服务是否真正高可用,及时发现系统薄弱的环节,提前准备好预案减少故障恢复时间。为了验证 MongoDB 是否真正高可用,我们在线下搭建了 MongoDB 集群:



同时,我们编写脚本模拟用户 MongoDB 数据插入和读取,基于好大夫在线自研故障演练平台,对机器进行故障注入,查看各种故障对用户的影响。故障演练内容 CPU、内存、磁盘、网络和进程 Kill 等操作,详情如下图所示:



实验结果:

1、CPU、磁盘填充和磁盘负载对 MongoDB 集群影响较小。


2、内存满载可能会发生系统 OOM,导致 MongoDB 进程被操作系统 Kill,由于 MongoDB 存在数据副本和自动主从切换,对用户影响较小。


3、网络抖动、延迟和丢包会导致 mongos 连接服务器时间变长,客户端卡顿的现象发生,可通过网络监控的手段监测。


4、分别主动 Kill 掉 MongoDB 的主节点、从节点、仲裁节点、mongos、config 节点,对整个集群影响较小。


整体而言,MongoDB 存在副本和自动主从切换,客户端存在自动检测重连机制,单个机器发生故障时对整体集群可用性影响较小。同时,可增加对单机器的资源进行监控,达到阈值进行报警,减小故障发现和恢复时间。

总结


1、MongoDB 的使用

  • MongoDB数据写入可能各个分片不均匀,此时可以开启块均衡策略;由于均衡器会增加系统负载,最好选择在业务量较小的时候进行;

  • 合理选择分片键和建立索引,会使你的查询速度更快,这个要具体场景具体分析。


2、迁移数据

  • 必须保留唯一标识数据的字段,最好是主键id,方便校验数据;

  • 一定要考虑多进程,脚本要自动化,缩短迁移时间和减小人工介入;

  • 迁移过程中,要时刻关注数据库、中间件及应用相关指标,防止导出导入数据影响正常业务;

  • 要在同样配置的环境下充分演练,提前制定数据比对测试用例,以防止数据丢失;

  • 每一步线上操作(如切换读写),都要有对应的回滚计划,最大限度降低对业务的影响;


作者介绍:孙文华,好大夫在线系统开发工程师,专注于监控系统、缓存系统和公共基础系统开发,对容器化和文件存储也有涉猎和研究。

2020 年 11 月 17 日 15:011422

评论 12 条评论

发布
用户头像
感谢分享,学到了
2020 年 11 月 17 日 15:33
回复
用户头像
很棒的技术文章,满满的干货
2020 年 11 月 17 日 15:31
回复
用户头像
学习一下
2020 年 11 月 17 日 15:22
回复
用户头像
有内容啊,很丰富啊
2020 年 11 月 17 日 15:20
回复
用户头像
感谢楼主为我们迁移数据提供了一种新思路,maxwell思路可以
2020 年 11 月 17 日 15:20
回复
用户头像
可以看到文字内容了
2020 年 11 月 17 日 15:19
回复
用户头像
文章是否有点水?
2020 年 11 月 10 日 09:40
回复
通篇的这个不合适那个不合适,没见到切实的不合适的理由,目测也就是“mongodb我比较熟/比较喜欢,所以比较稳”所以才这么决定的吧~
2020 年 11 月 18 日 09:25
回复
熟和稳 这个理由已经很充分了。
2020 年 11 月 20 日 19:19
回复
用户头像
就标题和图,没有内容。
2020 年 11 月 10 日 09:36
回复
用户头像
这....文字内容呢?

2020 年 11 月 10 日 09:07
回复
用户头像
看不到文章
2020 年 11 月 10 日 09:06
回复
没有更多评论了
发现更多内容

99%的人都能看懂的分布式系统「补偿」机制

华为云开发者社区

分布式 高可用 系统

iOS造轮子 - UITableView字母索引条

iOSer

ios 面试题 UITableView iOS面试

架构师训练营第一期-第二周课后-作业二

架构师训练营第一期

滴滴开源AgileTC:敏捷测试用例管理平台

滴滴技术

开源项目 滴滴技术 滴滴开源

免费CA证书安装配置与背后原理浅析

陈德伟

LeetCode题解:83. 删除排序链表中的重复元素,递归,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

架构师 0 期 | 大数据相关技术

刁架构

架构师训练

Week02

SuperLab

HashMap源码解析

彭阿三

hashmap HashMap底层原理

网易伏羲问鼎全球AI文创大赛:用户可零门槛生产音视频动画

核桃Eason

人工智能 AI 动画 网易

LeetCode题解:83. 删除排序链表中的重复元素,迭代,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

flutter之踩坑的日子(3)

霜蓝手环

Flutter Android Apk

学习Java的三个阶段(学习目标+知识点),一起努力吧!

Java架构师迁哥

小前端探索HTTP

Lam

JavaScript 面试 前端 网络 HTTP

你的页面健康吗?

Lam

JavaScript 前端 浏览器 性能分析 web前端

C++的匿名函数(lambda表达式)

良知犹存

c++ 编程开发

线上医疗未来的发展

anyRTC开发者

ios 音视频 WebRTC RTC 安卓

Git:改变世界的一次代码提交

华为云开发者社区

git Linux 代码

week03

SuperLab

不一样的面向对象(一)

书旅

php 面向对象

高难度对话读书笔记—认知篇

wo是一棵草

极客大学架构师训练营-架构师技术图谱-大作业二

叶鹏

网上黑平台系统风控不能出款怎么办

其实很简单

大数据 网络 系统检测

JS中的Timing API

Lawliet

双亲委派模型与 Flink 的类加载策略

Apache Flink

flink

Java经典面试题详解,突围金九银十面试季(附详细答案)

Java架构师迁哥

聊聊布隆过滤器

海星

10个常见的软件架构模式

GuoYaxiang

架构模式 软件架构 架构设计

极客大学架构师训练营 - 同城快递业务架构设计 - 大作业一

叶鹏

深入分析CRM系统对现代企业的作用

Philips

企业管理 CRM 客户关系管理

10周作业-微服务

飞雪

AI如何在普惠金融的探索中发挥作用?

AI如何在普惠金融的探索中发挥作用?

如何将数亿MySQL数据无缝迁移到MongoDB?-InfoQ