上次《百亿级数据DB秒级平滑扩容!》之后,很多朋友提出问题,说如果不是“双”扩容,能不能在不影响服务的情况下实现平滑迁移?典型的系统分层架构如下:(1)上游为业务层biz,实现个性化业务逻辑;(2)中游是service层service,封装了数据访问;(3)下游是数据层db,存储固化的业务数据;服务分层架构的好处是服务层屏蔽了下游数据层的复杂性,比如缓存、分库分表、存储引擎等存储细节不需要上传给调用者暴露,只向上游提供一个方便的RPC访问接口。当某些数据层发生变化时,所有调用者不需要升级,只需要服务层。互联网架构往往面临以下需求:(1)底层表结构的变化:当数据量很大时,数据表中的一些属性被增删改查。(2)分库数量的变化:由于数据量的不断增加,底层分库的数量并没有呈指数增长。(3)底层存储介质的改变:底层存储引擎从一个数据库变为另一个。各种需求都需要数据迁移。如何在不停止迁移过程的情况下顺利迁移数据,保证系统的持续服务,是本文要探讨的问题。方案一:宕机解决方案在讨论平滑数据迁移方案之前,我们先来看一下非平滑宕机数据迁移方案,主要分为三个步骤。第一步:挂上类似“为了更好地为用户提供服务,服务器将在凌晨0:00-0:400之间进行停机维护”的公告,并在相应时间段内关闭。在此期间,系统没有流量。第二步:关机后开发离线数据迁移工具进行数据迁移。针对第一节中的三类需求,将分别开发不同的数据迁移工具。(1)底层表结构变更需求:开发旧表导入新表工具;(2)分库数量变更需求:开发2库转3库工具;(3)底层存储介质改造需求:开发Mongo导入Mysql工具;第三步:恢复服务,将流量切换到新库。不同的需求可能涉及不同的服务升级。(1)改变底层表结构的需求:需要升级服务才能访问新表;(2)分库数量变更需求:服务不需要升级,只需要变更查库的路由配置即可;(3)改变底层存储介质的要求:服务升级以访问新的存储介质;总的来说,宕机解决方案比较直观简单,但是对服务可用性有影响。很多游戏公司可能会在服务器升级、游戏分区合并区域等方面采用类似的解决方案。这种方案除了影响服务的可用性之外,还有一个缺点,就是必须在规定的时间内完成升级。这会给研发、测试、运维同学带来很大的压力。一旦出现数据不一致等问题,必须在规定时间内完成。解决了,不然只能回滚。根据经验,压力越大的人越容易犯错,这个缺点在一定程度上是致命的。无论如何,停机解决方案不是今天讨论的重点。下面我们来看看常见的平滑数据迁移方案。方案二:日志追踪方案日志追踪方案是一种高可用的平滑迁移方案。这个解决方案主要分为五个步骤。在数据迁移之前,上游业务应用通过旧服务访问旧数据。第一步:服务升级,记录“旧数据库上的数据修改”日志(这里的修改是数据的insert、delete、update)。该日志不需要记录详细数据,主要记录:(1)修改库;(2)修改表;(3)修改后的唯一主键;新增了哪些行,修改的数据格式是什么,不需要详细记录。这样做的好处是无论业务细节如何变化,日志的格式都是固定的,保证了方案的通用性。该服务的升级风险较小:(1)写入接口为少量接口,改动较少;(2)升级仅增加部分日志,对业务功能无影响;第二步:开发数据迁移工具,进行数据迁移。此数据迁移工具与离线迁移工具相同,都是将旧数据库中的数据迁移到新数据库中。这个小工具的风险比较小:(1)整个过程还是老库提供在线服务;(2)小工具复杂度低;(3)任何时候发现问题,都可以将新库中的数据重新去掉;(4)可以限速慢慢迁移,技术同学没有时间压力;数据迁移完成后,是否可以切换到新数据库提供服务?答案是否定的,在数据??迁移过程中,旧的数据库仍然在线提供服务,数据库中的数据随时可能发生变化。这种变化在新的数据库中没有体现出来,所以旧库和新库的数据不一致,所以不能直接切库,需要对数据进行均分。哪些数据发生了变化?第1步日志中记录的是变化的数据。Step3:开发一个读取日志和迁移数据的小工具,均衡Step2迁移数据过程中产生的差异数据。这个小工具需要做的是:(1)读取日志,得到哪个库、哪个表、哪个主键发生了变化;(2)读出旧库中主键对应的记录;主键对应的记录被替换;无论如何,原则是数据以旧数据库为准。这个小工具的风险也很小:(1)整个过程还是旧库提供在线服务;(2)小工具复杂度低;(3)任何时候发现问题,大不了从第2步重新开始;(4)日志可以限速慢速回放,技术同学没有时间压力;日志重放后,是否可以切换到新的数据库提供服务?答案仍然是否定的,在日志回放过程中,旧库中的一些数据可能发生了变化,导致数据不一致,所以无法切库,需要进一步读取日志来均衡记录。可以看出,重放日志匹配数据的程序是一个while(1)程序,新库和旧库中的数据也会是一个“无限逼近”的过程。数据什么时候才能完全一致?第四步:在不断的回放日志和绑数据的过程中,开发一个数据校验小工具,将旧库中的数据和新库中的数据进行对比,直到数据完全一致。这个小工具的风险还是很小的:(1)整个过程还是老库提供在线服务;(2)小工具复杂度低;(3)随时发现任何问题,最差从第2步开始;(4)数据可以在有限的速度下慢慢比较,技术同学没有时间压力;第五步:数据比对完全一致后,将流量迁移到新数据库,新数据库提供服务完成迁移。如果第4步的数据一直99.9%一致,不是完全一致,这是正常的。可以把一个二级老库做成readonly,等到logreplay程序完全赶上数据,再切库切流量。至此升级完成,全程可以继续在线提供服务,不影响服务的可用性。方案三:双写方案双写方案也是一种高可用的平滑迁移方案。该方案主要分为四个步骤。在数据迁移之前,上游业务应用通过旧服务访问旧数据。第一步:服务升级,在新数据库上进行“对旧数据库的数据修改”(这里的修改是插入、删除、更新数据),也就是所谓的“双写”,主要修改操作包括:(1)旧库和新库同时插入;(2)同时删除旧库和新库;(3)老库和新库同步更新;因为没有数据,所以双写老库和新库的效果行可能不一样,但这完全不影响业务功能。只要数据库不被切掉,旧的数据库仍然会提供业务服务。该服务的升级风险较小:(1)写入接口为少量接口,改动较少;(2)新库的写操作是否执行成功对业务功能没有影响;step2:开发数据迁移工具,进行数据迁移。这个数据迁移工具在本文中已经是第三次出现了,将数据从旧数据库迁移到新数据库。这个小工具的风险比较小:(1)整个过程还是老库提供在线服务;(2)小工具复杂度低;(3)任何时候发现问题,都可以将新库中的数据重新去掉;(4)可以限速慢慢迁移,技术同学没有时间压力;数据迁移完成后,是否可以切换到新数据库提供服务?答案是肯定的,因为前置步骤是双写的,所以理论上数据迁移完成后,新旧数据库中的数据应该是完全一致的。在迁移数据的过程中,旧库和新库同时进行双写操作。数据迁移完成后如何证明数据完全一致?如上图所示:(1)左边是旧数据库中的数据,右边是新数据库中的数据;(2)按照主键从min到max的顺序,分段限速迁移数据。假设数据段已经迁移到now,分别讨论数据迁移过程中的修改操作:数据一致性不被破坏。假设在迁移过程中进行了双删除操作,分为两种情况。情况一:假设删除的数据属于[min,now]范围,即迁移已经完成,旧库和新库都删除了数据,数据一致性没有被破坏;情况2:假设删除的数据属于[now,max]范围内,即还没有完成迁移,旧库中的delete操作会影响行数为1,而新库中的delete操作数据库会有0行的影响,但是数据迁移工具不会将旧库中删除的数据迁移到新库中,数据一致性仍然没有被破坏;假设在迁移过程中进行了两次更新操作,可以认为更新操作是删除加插入操作的复合操作,所以数据仍然是一致的,除非,在一个非常极端的情况下:(1)date-migrate-tool只是从旧数据库中取出一条数据X;(2)在X插入新数据库之前,旧数据库和新数据库只是复查X.delete操作;(3)date-migrate-tool将X插入新库;这样新库就会比旧库多一个数据X。但无论如何,为了保证数据的一致性,切库前还是需要进行数据校验。第三步:数据迁移完成后,需要使用一个数据校验的小工具,将旧库中的数据与新库中的数据进行对比。以图书馆资料为准。这个小工具的风险还是很小的:(1)整个过程还是老库提供在线服务;(2)小工具复杂度低;(3)随时发现任何问题,最差从第2步开始;(4)数据可以在有限的速度下慢慢比较,技术同学没有时间压力;第四步:数据完全一致后,将流量切换到新数据库,完成数据的平滑迁移。至此升级完成,全程可以继续在线提供服务,不影响服务的可用性。总结起来,对于互联网上很多“大数据量、大并发、业务复杂度高”的业务场景,在:(1)底层表结构的变化;(2)分库数量的变化;(3)底层存储介质在众多变化的需求下,需要进行数据迁移,完成“数据平滑迁移、迁移过程中不停机、系统持续服务”有两种常见的解决方案。追日志方案,五步走:(1)升级服务,记录“旧库数据修改”日志;(2)开发数据迁移工具,进行数据迁移;(3)开发读取日志小工具,均衡数据差异;(4)开发数据比对工具,验证数据一致性;(5)将流量切换到新的数据库,完成平滑迁移;双写方案,四步:(1)service进行升级,记录“旧库数据修改”,双写新库;(2)开发数据迁移工具,进行数据迁移;(3)开发数据比对工具,验证数据一致性;(4)流量切换到新数据库,完成平滑迁移;想法比结论更重要。
