本文最初发布于王斌的个人博客,经原作者授权由 InfoQ 中文站翻译并分享,原文地址:
https://www.binwang.me/2020-11-29-Keep-Data-Consistency-During-Database-Migration.html
当一个系统存在很长一段时间后,经常会使用更新的技术来提高性能、可维护性或添加新特性。其中一个变化可能会是使用哪个数据库。这可能是最困难的改变。在迁移过程中,有两个数据源,这使得该系统成为一个分布式系统。在分布式系统中,保持数据一致非常困难,而且很容易出错。在本文中,我们将探索一种在迁移期间保持数据一致性的方法,并且这种方法的停机时间较短。
为了使用本文描述的方法,需要满足一些要求:
制定以下迁移步骤的两个基本想法:
下面是具体步骤。
首先,我们需要源数据库可以导出一致的数据。标记好已转储的位置。例如,在 MySQL 中,可以在使用mysqldump
转储数据库时带上--master-data
选项,这样生成的文件中就会记录 bin log 日志的位置(使用文档)。从源数据库获得所有数据后,可以将它们插入目标数据库。
因为这是第一步,所以即使失败了也很容易处理:重新开始即可。因此,重要的是,在导入转储数据时,任何错误都要捕获。
下一步是捕获源数据库的更改。例如,在 MySQL 中,可以使用bin log捕获更改并将其插入到目标数据库中。因为上一步记录了开始位置,所以我们知道从哪里开始解析和导入更改。
在导入时保持更改顺序非常重要。所以最好是只使用一个进程来解析和导入更改。这个步骤非常具有挑战性:这一步性能很重要。同步所有更改的时间就是迁移所需的停机时间。
我们还需要确保,即使出现系统故障,也不会错过任何更改或多次导入任何更改。因此,记录更改日志的位置非常重要。我们可以使用与导入数据相同的事务将位置写入目标数据库,这非常方便。这样,位置就可以与我们导入的数据保持同步。
使用单一数据源是一种保持数据一致性的简单方法。到目前为止,我们使用源数据库作为单一数据源,并将更改同步到目标数据库。我们不想让其他写操作把目标数据库搞乱。因此,我们需要设置目标数据库的权限来拒绝来自客户端的所有写操作。例如,在 MySQL 中,只向客户端授予表的 select 权限,并拒绝其他操作。我们允许读取权限,这样在下一步中就可以比较读取结果。
下一步是让客户端同时对源和目标数据库进行读取和写入。
我们希望首先读/写源数据库。如果没有权限错误,则使用此结果,否则使用来自目标数据库的读/写结果。
对目标数据库的读/写操作有两个目的:
如果你想确保目标数据库能够处理这些负载,那么在一段时间内允许对目标数据库进行读/写操作是个好主意,但这只是作为一个验证,在那之后,目标数据库中的数据会不一致。因此,在我们验证了目标数据库能够处理系统流量之后,我们需要清理目标数据库,并再次从步骤 1 开始。(在这些步骤中,我们不需要修改客户端代码)。
对于错误处理,有两个关键点:
客户端代码如下:
db_operation() {
try {
source_result = source_db_operation()
} catch (PermissionException e) {
return target_db_operation()
}
async {
// do the following things async so it will not impact the performance
try {
target_result = target_db_operation()
compare_result(source_result, target_result)
} catch (Exception e) {
log_error(e)
}
}
return source_result
}
在对目标数据库的读写操作有信心之后,就可以进行切换了。我们通过更改数据库权限来切换数据库。首先,我们拒绝客户端对源数据库的所有访问。然后等待更改完全同步到目标数据库。在此期间,系统是停机的。因此,从源数据库到目标数据库的更改同步速度决定了它的停机时间。
在目标数据库完全同步之后,我们可以为所有客户端赋予目标数据库权限。在此之后,系统就再次在线了,数据库已完全切换。
如果到目前为止一切顺利,那就太好了。但情况并非总是如此。也许目标数据库无法处理新的流量(这就是我们说步骤 4 的测试很重要的原因),在这种情况下,我们就需要回退到源数据库。
如果是在迁移期间丢失了提交的数据,那不是什么大问题,回退也比较简单:
如果保存提交的数据并保持一致性非常重要,那么在第 5 步之前,我们应该设置一种机制来捕获从目标数据库到源数据库的更改,并在第 6 步之后标记更改位置。那么,回退步骤将是下面这样:
从目标数据库到源数据库的同步非常危险且难以测试,因此,在步骤 4 中测试目标数据库能否处理新的流量就非常重要。
切换到目标数据库之后,我们就可以清除访问源数据库的代码了。这样,数据库迁移就大功告成了!
领取专属 10元无门槛券
私享最新 技术干货