面试项目系列1----数据迁移项目

590 阅读7分钟

如何把数据迁移项目给融入到面试?要注意的点有哪些?

背景:现在由于系统改造,要进行系统层面的数据迁移,把数据从旧库旧表全部迁移到新库。需要注意的点有哪些?

初级版:

1:设计分库分表的分键位,并设计唯一id。

2:支持迁移过程中中断恢复,毕竟迁移过程是一个持续时间较长的过程,中间出问题太正常不过了,如何做到中断恢复是一个很重要的事。

3:支持增量迁移。同步过程中的新增数据,也不能丢。

4:数据校验。得保证自己确实是迁移成功了。

以上是大部分人都听说过也能理解的问题,但是在实际迁移中,问题多的远远超出你的想象。咱们来聊聊具体会有哪些问题?

5:对外暴露的id怎么处理?你数据迁移了,会生成新的id吗?绝对有的系统避免不了,那就牵涉到你之前对外暴露的id怎么办?

6:迁移过程中你会调用其他人的接口,怎么保证在他们接口出问题的时候,业务不会一直找你去解决问题?

7:那些之前系统可能有redis缓存,mq消息,es数据,job的延迟任务等等,这些数据可能是老的,怎么处理?

8:如果某一个模块迁移失败,需要对这个店铺回滚,或者重新迁移一次,怎么支持?

9:怎么查看迁移进度?

10:如何提升迁移速度?

如何解决?咱们一点点分析。

问题1:分表位之类的其实还好,跟业务挂钩,尽量平均。如果你的业务不太可能出现那种动不动就几百万的数据倾斜,其实按店铺维度就行,hash取模也就够了。如果是特殊的业务,店铺数据倾斜严重,那就找一个其他分表位。比如客户表的话,就不能按店铺id,按客户id就可以。唯一id这个也比较简单,自己做一个唯一id生成组件即可。日期6位,业务编号3位,接口版本2位,弹性位2位,分标编号3位,预留3位,以及11位的自增id,共30位。其他都还好,主要是自增id。做法就是用号段发射器,其实就是在数据库弄一条数据,初始化的时候提前设置好,比如我们设置号段1000,那就先更新下最大号段为1000,然后在内存自增,如果是另一个机器去申请的时候,由于有mysql的锁存在,大家都会执行update xx set maxix=maxid + step 这种,然后每个机器拿到的号段都会不一样,第一个比如是0-1000,第二个机器就是1000-2000,大家自增互不影响。当然,每个机器自己执行的时候肯定涉及到一系列的并发问题,我们按照初始化-数据库加载-内存自增之类步骤做就行。当然也可以做一些优化,比如号段拉取频率低于15分钟,说明并发有点大,我们把步长自动扩容下,超过45分钟我们就缩一点,甚至还可以做个双缓冲,一个在拉取号段后,异步线程另一个缓冲也去拉,这样就避免了拉取号段的卡顿时间了。

问题2:支持迁移过程中中断恢复。首先我们能想到的就是在拉取-写入这个过程中,肯定要记录一下已经成功的最大id。比如我们处理1-10000的数据,500条批次处理,那么在执行完500条数据后,一定会把第500条数据的id写入到任务表,所以我们不难想到肯定有一个任务表代表这次迁移,还有一个批次表,代表每一次迁移这个业务的批次。这样我们能对这个迁移任务就会有个把控,如果有一批次插入失败了,再次启动查询就能接着上次执行。如果失败了原因也要更新上。一般出错了是要停止这个任务的,当然也可以设置重试机制,超过指定配置次数就需要人工干预了。

问题3:增量数据有两种方案。一种是让商户停止数据接收,相当于完成1阶段的存量迁移后,在半夜暂停下,把增量数据给写入。再切掉老入口。这种方案优点就是简单,缺点就是需要停一段时间。而且有可能有mq之类的数据还在灌入,需要想办法把这些数据给转到新系统处理。别觉得没人用这么垃圾的方案,实际上在时间比较紧张的情况下,这不失为一个快速迁移的方案。第二种方案就是比较熟悉的了,canal监听mysql数据,扔到mq,消费,写入新库。根据更新时间对数据进行取舍,看是保留新数据还是老数据。但是这里同样有可能有其他中间件的数据,这种可能就得自己业务统计处理,写侵入代码把数据也同步一份过去。

问题4:校验迁移成功。其实一般来说,在测试环境能保证字段都没问题的情况下,只需要校验下相同条件下的count就够了。当然,要做的复杂一点,完全也可以把所有字段都弄起来搞个md5,相当于每行数据都有一个唯一值,迁移后看唯一值是否一致。

问题5:对外暴露id,要么就id不变,新表的id设置的初始id要远大于之前的最大id,这样改动比较小。否则的话,就得找上下游对接,把id一块更新了。

问题6:迁移过程调用外部接口的时候,异常数据一定要记录,提供接口出来查询,让测试碰到问题优先先去查接口或看日志。定位不到问题再找你。

问题7:没说的,这些数据都要重建。

问题8:提前准备清理脚本,支持重新迁移。

问题9:迁移进度,正常来说,就是已经迁移的数量/原表的数据。已经迁移的数据其实还好,迁移的时候会更新他,获取这个非常容易。但是获取原表数据的数量就没那么简单了。总不能selet count吧?而且这个数字还不是一个定值,他会不断变化的。但是我们发现这个数量其实只会新增,并且跟日期有关,比如我们如果提前统计好这个表2022年3月3号之前的数量200W,那么在统计现在的数据的时候,加个时间条件去count,再加回之前200W速度就会快很多。所以我们完全可以写一个定时器,搞一个表,把老数据每一天都count下记录,这样即便2年数据也才365*2条数据,这个时候我们去sum下,很容易就能拿到总数。迁移进度也就很容易算了。

问题10:说几个优化点。迁移最大的特点就是很容易出现慢sql,尤其是第一次查询,因为滚动id第一次一般都是0,sql就是select * from xx where id >0 and pid = xx limit 500,假设你有一个大店铺,pid数据很多重复的,他就会走id索引,极容易全表扫。怎么优化呢?我们可以设置一个段,比如5W,这个sql多加一个条件 and id < min+5W,第一次就会是>0 <5W,这种sql数据量大的时候肯定是快很多的,因为最多也就扫描5W数据了。第一次执行完可能id滚动到2000了,那第二次就是>2000 小于52000。另外迁移的时候尽量不要单线程执行。单线程读是没啥问题,可以多线程写。比如我们弄个线程池去写,不过这里有个问题,这样做的话,成异步了。那任务的完成状态就不好去改了,所以我们得等所有线程执行完毕才能去执行。比如我们在查数据扔线程池的时候,redis key+1 执行完key-1,主线程卡主循环,什么时候key是0了就返回。类似可重入锁的思路