当前位置:首页 > 投稿 > 正文

从单表到分表实现数据平滑迁移(单表可以在线转换为分表)

背景

初到新公司接手支付中心系统,发现支付表单表数据量达到了 7 亿多,震惊之余不由的脊背发凉,这个系统运行犹如走钢丝,稍微有点风吹草动就可能造成线上事故,DBA 天天催着要数据迁移,给出大表解决方案,给出排期。

当你担心一件事要出问题,他就一定会出问题,不得不说这墨菲定律真™️准。营销部门搞活动,支付单量激增,客服反应好多用户投诉明明支付成功了,但还是处于支付中状态。打开日志排查原因,就是因为数据库响应慢,支付状态未能更新成功,导致未能及时通知上游系统支付成功结果。(当然支付逻辑也存在问题,这点不在此文讨论范围内)

痛定思痛,分库分表和数据迁移迫在眉睫。

分库?分表?

什么时候需要分库

分库主要是解决并发量大的问题,数据库的连接数是有限的,当读或者写的QPS过高,导致数据库连接数不够时,就需要考虑分库,通过增加数据库实例的方式来增加连接数,从而提升系统的并发性能。

什么时候需要分表

分表主要时应对数据量大的情况,当一个表的数据量过大时,不论是读还是写的性能都会出现明显下降,一张表的数据量最好控制在1000万以内(看硬件性能)。

业务增量分析及容量规划

首先通过计算得到高峰期QPS每秒约50,并发量不算特别高,所以没有必要做分库来分担并发量。

在业务高峰期的月份每日的支付单量约270多万,业务低峰期的月份每日的支付单量约130多万,每年大概的单量为6亿单左右,

按照3到5年的规划,每张表约1000万数据,计划分256张表。

我们目前数据库配置是32C/64G/3T SSD,满足需求。

数据迁移

数据迁移分析及遇到的问题:

  • 使用业务单号的hash值对256取模(hash(业务唯一ID)%256),得到表的索引值。之前单表存在唯一的业务单号,直接使用即可。
  • 需要将旧库新增的实时数据同步到新库,并记录开始数据的ID。
  • 需要将存量数据同步到新库,终止点为1中开始数据的ID。
  • 考虑切流量过程中的稳定性和可回滚能力,需要将实时数据双向同步,即新库新增的数据也需要同步到旧库。
  • 迁移完成,需要做新旧库数据的比对工作。

整体迁移方案

下面按照迁移步骤来逐一讲解:

1. 增量数据同步

将实时新增的数据同步到新库的表中。这阶段利用的 Canal、RocketMQ 中间件实现,Canal 获取 MySQL BinLog 日志,发送到 RocketMQ 指定的 Topic,消费者对数据做处理后插入到新的库表。

增量数据同步问题

  1. 需要记录一下增量数据同步的开始 ID,这个 ID 作为存量数据的终止点。
  2. RocketMQ 消费消息要保证顺序性,需要指定消费模式为顺序消费 consumeMode = ConsumeMode.ORDERLY,顺序消费需要注意,如果消费消息异常,会导致消息阻塞。
  3. 在增量数据同步时,需要将 update 语句转为 insert 语句,因为我们先执行增量数据同步,在这个过程中可能会有对存量的数据进行更新的操作,如果不将 update 转为 insert 的话,在迁移存量数据的时候,迁移数据在存入新库前,发生了更新,更新先到的新库,这时会更新 0 条记录,因为存量数据还未插入到新库,就会发生更新丢失的情况。binlog 是全部字段发送的,经过解析可以 insert 全部的字段。

2. 存量数据同步

存量数据同步使用的是游标方式,从旧库中分批查询数据,经过业务单号hash取模后批量插入到新库,直到迁移到数据 ID 为增量开始的数据 ID 时结束。

3. 数据双向同步

为了保证迁移过程中的系统稳定性,在出现问题时,要有可回滚的能力,这就需要新库中的新增数据也要同步到旧库中。

那这就存在消息无限循环问题了,旧库中新增一条数据,Canal 接收到这条数据的 binlog 消息,将该数据写入到新库中,于是新库也产生一条 binlog 消息,Canal 接收到消息,将该数据写入到旧库中,....不断的循环 。

我们的方案是给数据染色,旧表需要新增一个标识字段来说明是旧表产生的数据,标识字段默认值为 0。同样新表也需要增加一个标识字段说明是新表产生的数据,标识字段默认值为 1。标识字段一定要指定默认值,这样业务代码就不需要改动了。

当旧库向新库同步时,消息过滤组件会判断标识字段是否为 0,如果为 0 则说明是旧库产生的数据,就同步到新库,否则就会丢弃消息。新库向旧库同步也是如此,只不过新库的标识字段为 1。这样就解决了数据双向同步产生的消息循环问题了。

数据校验

数据迁移完成,需要核对逐条逐个字段是否一致,如果不一致需要记录下数据和报警通知。

以旧库为基准,查询每条数据在新库中是否存在,并且每个字段是否一致。

以新库为基准,查询每条数据在旧库中是否存在,并且每个字段是否一致。

对于不一致的数据记录到 diff 表中,扫描 diff 表再次、三次做校验,如果还是不一致,就需要手动校验了。再次校验的目的是可能新旧库同步存在延迟。

灰度流量切换

流量灰度方案及数据源切换方案:

  1. 新增新库分表代码,分表使用的是 ShardingSphere 中间件,访问旧库和新库可以根据配置来切换。
  2. 通过配置中心配置切量白名单,如果命中白名单则会走分表数据源,否则就会走旧库。
  3. 数据校验组件如果无报警,修改配置中心配置来慢慢放量,直到全部切量完成。
  4. 如果数据有问题,可以修改配置及时切回旧库。

数据分表之后的查询方案

数据分表之后对 B 端后台业务的查询是有影响的,之前查询一张表,现在要查询 256 张表吗?

对此我们做了数据归集方案,将 256 张表的数据都归集到 TiDB 分布式数据库中,TiDB 在大数据量的查询效率上大大优于 MySQL。

利用 Canal 将分表数据做归集

TiDB 库中的表可以根据 B 端业务查询的字段需求,多设置几个索引,以提高查询效率。

总结

经过一个多月的奋战,终于将 7 亿多的表拆分完成,无事故,无回滚,无业务代码侵入平稳的完成了数据迁移和分表操作。