随系统规模逐渐增长,总会遇到更换数据库问题。
- 对MySQL分库分表后,需要从原来的单实例数据库迁移到新的数据库集群
- 系统从传统部署方式向云上迁移的时候,也需要从自建的数据库迁移到云数据库
- 一些在线分析类的系统,MySQL性能不够用的时候,就需要更换成一些专门的分析类数据库,比如说HBase
整个迁移过程,既不能长时间停服,也不能丢数据。如何不停机安全地迁移数据更换数据库。
1 不停机更换数据库
设计迁移方案的时候,要做到,每步都可逆。要保证,每执行一个步骤后,一旦出现问题,能快速地回滚到上一个步骤。以订单库为例子。
先把旧库数据复制到新库。因为旧库还在服务线上业务,所以不断会有订单数据写旧库,不仅要往新库复制数据,还要保证新旧两个库的数据是实时同步。要用一个同步程序,实现新旧两个数据库实时同步。
怎么实现两个异构数据库间的数据实时同步?Binlog实时同步数据。如果源库不是MySQL就麻烦,但也可以参考我们讲过的,复制状态机理论来实现。这一步不需回滚,只增加了一个新库和一个同步程序,对系统的旧库和程序都没有任何改变。即使新上线的同步程序影响到了旧库,只要停掉同步程序。
改造订单服务,业务逻辑部分不变,DAO层改造:
- 支持双写新旧两库,并预留热切换开关,能通过开关控制三种写状态:只写旧库、只写新库和同步双写
- 支持读新、旧两库,预留热切换开关,控制读旧库or新库
上线新版订单服务,订单服务仍只读写旧库,不读写新库。让这新版订单服务稳定运行至少1~2周,期间除验证新版订单服务稳定性,还要验证新、旧两个订单库中的数据是否一致。这个过程中,如果新版订单服务有问题,可以立即下线新版订单服务,回滚到旧版本的订单服务。
稳定段时间后,开启订单服务的双写开关,同时停掉同步程序。这个双写的业务逻辑,一定先写旧库,再写新库,并以写旧库的结果为准。
旧库写成功,新库写失败,返回写成功,但记录日志,后续用这日志验证新库是否还有问题。旧库写失败,直接返回失败,就不写新库。不能让新库影响现有业务可用性和数据准确性。上面这过程若出现问题,可关闭双写,回滚到只读写旧库状态。
切换到双写后,新、旧库数据可能存在不一致:
- 停止同步程序和开启双写,这两个过程很难做无缝衔接
- 双写策略也不保证新旧库强一致,这时候要上线一个对比和补偿程序,对比旧库最近数据变更,然后检查新库中的数据是否一致,若不一致,还要补偿
开启双写后,还要至少稳定运行几周,期间不断检查,确保不能有旧库写成功,新库写失败case。对比程序也没发现新旧两库数据有不一致,这时就可认为,新旧两库数据一直保持同步的。
接下来灰度发布把读请求切到新库。期间若初问题,可再切回旧库。全部读请求都切换到新库后,读写请求就已经都切换到新库,实际的切换已完成。
再稳定一段时间后,停掉对比程序,把订单服务写状态改为只写新库。旧库就可下线。整个迁移过程中,只有这步不可逆。但这步主要操作就是摘掉不再使用的旧库,对在用的新库并没有什么改变,实际出问题的可能性已非常小。
就完成在线更换数据库的全部流程。双写版本的订单服务也就完成了它的历史使命,可以在下一次升级订单服务版本的时候,下线双写功能。
2 实现对比和补偿程序
难度
要对比的是两都在随时变换的数据库中的数据。没有类似复制状态机这样理论上严谨实际操作还很简单的方法。但还是可根据业务数据实际情况,针对实现对比和补偿,经过一段时间,把新旧两个数据库的差异,逐渐收敛一致。
订单这类时效性强数据,较好对比和补偿。因为订单一旦完成几乎不会再变,对比和补偿程序,就可依据订单完成时间,每次只对比这时间窗口内完成的订单。补偿逻辑简单:发现不一致,直接用旧库订单数据覆盖新库订单数据。
切换双写期间,少量不一致订单数据,等订单完成后,会被补偿程序修正。后续只要不是双写时,新库频繁写失败,就可保证两库数据完全一致。
麻烦的是更一般case
如商品信息随时可能变化。若数据上有更新时间,对比程序可利用这更新时间,每次在旧库取一个更新时间窗口内的数据,去新库找相同主键的数据对比,发现数据不一致,还要对比更新时间。若新库数据更新时间晚于旧库数据,可能对比期间数据发生变化,这种情况暂不补偿,放到下个时间窗口继续对比。时间窗口的结束时间,不要选取当前时间,而要比当前时间早点儿,如1min前,避免对比正写入的数据。
若数据连时间戳也没,只能去旧库读取Binlog,获取数据变化,去新库对比和补偿。
这些方法严格推敲都不是百分百严谨,都不能保证在任何情况下,经过对比和补偿后,新库数据和旧库完全一样。但大多数情况下,这些实践方法还是可以有效地收敛新旧两个库的数据差异,酌情采用。
总结
设计在线切换数据库的技术方案,首先要保证安全性,确保每一个步骤一旦失败,都可以快速回滚。此外,还要确保迁移过程中不丢数据,这主要是依靠实时同步程序和对比补偿程序来实现。
切换过程按顺序:
- 上线同步程序,从旧库中复制数据到新库中,并实时保持同步;
- 上线双写订单服务,只读写旧库;
- 开启双写,同时停止同步程序;
- 开启对比和补偿程序,确保新旧数据库数据完全一样;
- 逐步切量读请求到新库上;
- 下线对比补偿程序,关闭双写,读写都切换到新库上;
- 下线旧库和订单服务的双写功能。