互联网系统,经常会有数据迁移的需求。系统从机房迁移到云平台,从一个云平台迁移到另一个云平台,系统重构后表结构发生了变化,分库分表,更换数据库选型等等,很多场景都需要迁移数据。
在互联网行业,很多系统的访问量很高,即便在凌晨两三点也有一定的访问量。由于系统数据迁移,导致服务暂停几分钟,是很难被业务方接受的!本文我们就来聊一下,在用户无感知的前提下,如何设计不停机数据迁移方案!
数据迁移过程我们要注意哪些关键点呢?第一,保证迁移后数据准确不丢失,即每条记录准确而且不丢失记录;第二,不影响用户体验(尤其是访问量高的C端业务需要不停机平滑迁移);第三,保证迁移后的性能和稳定性。
数据迁移方案
挂从库
在主库上建一个从库。从库数据同步完成后,将从库升级成主库(新库),再将流量切到新库。
这种方式适合数据结构不变,而且空闲时间段流量很低,允许停机迁移的场景。一般发生在平台迁移的场景,如从机房迁移到云平台,从一个云平台迁移到另一个云平台。大部分中小型互联网系统,空闲时段访问量很低。在空闲时段,几分钟的停机时间,对用户影响很小,业务方是可以接受的。所以我们可以采用停机迁移的方案。步骤如下:
1,新建从库(新数据库),数据开始从主库向从库同步。
2,数据同步完成后,找一个空闲时间段。为了保证主从数据库数据一致,需要先停掉服务,然后再把从库升级为主库。如果访问数据库用的是域名,直接解析域名到新数据库(从库升级成的主库),如果访问数据库用的是IP,将IP改成新数据库IP。
3,最后启动服务,整个迁移过程完成。
这种迁移方案的优势是迁移成本低,迁移周期短。缺点是,切换数据库过程需要停止服务。
双写
老库和新库同时写入,然后将老数据批量迁移到新库,最后流量切换到新库并关闭老库读写。
这种方式适合数据结构发生变化,不允许停机迁移的场景。一般发生在系统重构时,数据结构会发生变化,如表结构改变或者分库分表等场景。有些大型互联网系统,平常并发量很高,即便是空闲时段也有相当的访问量。几分钟的停机时间,对用户也会有明显的影响,甚至导致一定的用户流失,这对业务方来说是无法接受的。所以我们需要考虑一种用户无感知的不停机迁移方案。
以笔者之前经历的用户系统重构为例,聊一下具体方案。当时的场景是这样的,用户表记录数达到3000万时,系统性能和可维护性变差,于是我们将用户中心从单体工程中拆分出来并做了重构,重新设计了表结构,而且业务方要求不停机上线!下面是我们当时的方案,步骤如下:
- 代码准备。在服务层对用户表进行增删改的地方,要同时操作新库和老库,需要修改相应的代码(同时写新库和老库)。准备迁移程序脚本,用于做老数据迁移。准备校验程序脚本,用于校验新库和老库的数据是否一致。
- 开启双写,老库和新库同时写入。注意:任何对数据库的增删改都要双写;对于更新操作,如果新库没有相关记录,需要先从老库查出记录,将更新后的记录写入新库;为了保证写入性能,老库写完后,可以采用消息队列异步写入新库。
- 利用脚本程序,将某一时间戳之前的老数据迁移到新库。注意:1,时间戳一定要选择开启双写后的时间点(比如开启双写后10分钟的时间点),避免部分老数据被漏掉;2,迁移过程遇到记录冲突直接忽略(因为第2步的更新操作,可能已经把记录拉到了新库);3,迁移过程一定要记录日志,尤其是错误日志,如果有双写失败的情况,我们可以通过日志恢复数据,以此来保证新老库的数据一致。
- 第3步完成后,我们还需要通过脚本程序检验数据,看新库数据是否准确以及有没有漏掉的数据
- 数据校验没问题后,开启双读,起初给新库放少部分流量,新库和老库同时读取。由于延时问题,新库和老库可能会有少量数据记录不一致的情况,所以新库读不到时需要再读一遍老库。逐步将读流量切到新库,相当于灰度上线的过程。遇到问题可以及时把流量切回老库
- 读流量全部切到新库后,关闭老库写入(可以在代码里加上热配置开关),只写新库
- 迁移完成,后续可以去掉双写双读相关无用代码。
利用数据同步工具
我们可以看到上面双写的方案比较麻烦,很多数据库写入的地方都需要修改代码。有没有更好的方案呢?
我们还可以利用Canal,DataBus等工具做数据同步。以阿里开源的Canal为例。
上面是Canal的原理图,
1,Canal模拟mysql slave的交互协议,把自己伪装成mysql的从库
2,向mysql master发送dump协议
3. mysql master收到dump协议,发送binary log给slave(canal)
4. canal解析binary log字节流对象,根据应用场景对binary log字节流做相应的处理
所以上面的用户系统数据迁移,就不需要开启双写了,服务层也不需要编写双写的代码,直接用Canal做增量数据同步即可。相应的步骤就变成了:
- 代码准备。准备Canal代码,解析binary log字节流对象,并把解析好的用户数据写入新库。准备迁移程序脚本,用于做老数据迁移。准备校验程序脚本,用于校验新库和老库的数据是否一致。
- 运行Canal代码,开始增量数据(线上产生的新数据)从老库到新库的同步。
- 利用脚本程序,将某一时间戳之前的老数据迁移到新库。注意:1,时间戳一定要选择开始运行Canal程序后的时间点(比如运行Canal代码后10分钟的时间点),避免部分老数据被漏掉;3,迁移过程一定要记录日志,尤其是错误日志,如果有些记录写入失败,我们可以通过日志恢复数据,以此来保证新老库的数据一致。
- 第3步完成后,我们还需要通过脚本程序检验数据,看新库数据是否准确以及有没有漏掉的数据
- 数据校验没问题后,开启双读,起初给新库放少部分流量,新库和老库同时读取。由于延时问题,新库和老库可能会有少量数据记录不一致的情况,所以新库读不到时需要再读一遍老库。逐步将读流量切到新库,相当于灰度上线的过程。遇到问题可以及时把流量切回老库
- 读流量全部切到新库后,将写入流量切到新库(可以在代码里加上热配置开关。注:由于切换过程Canal程序还在运行,仍然能够获取老库的数据变化并同步到新库,所以切换过程不会导致部分老库数据无法同步新库的情况)
- 关闭Canal程序
- 迁移完成。
此外,对于数据结构不改变的不停机数据迁移,也可以利用Canal处理。除了第3步DBA可以直接利用工具做老数据的迁移,其他步骤基本和上面一样。
希望本文对大家有所帮助。原创不易,如果感觉本文有帮助,有劳转发或点一下“在看”!让更多人收获知识!