文章详情 您在阅读帖子内容并对帖子进行投票之后,可发表回复。

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

分享到: 分享到QQ  分享到Twitter
作者:BigLoser    访问次数:1325    投票总数:0   
创建时间:2020-11-17 22:57:41   

在好大夫在线内部,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,方便校验数据;

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

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

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

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

 

帖子投票

名称 是否有价值
MongoDB群组的头像

MongoDB

MongoDB是一种面向文档的数据库管理系统,用C++等语言撰写而成,以解决应用程序开发社区中的大量现实问题。MongoDB由MongoDB Inc.于2007年10月开发,2009年2月首度推出,现以服务器端公共许可分发。

投票统计

是否原创:0%

0% Complete (success)

是否有价值:0%

0% Complete

是否有素质:0%

0% Complete (warning)

是否合法:0%

0% Complete