跨云迁移过程中的数据同步及一致性校验实践(一)

前言

随着互联网业务发展对容灾以及对访问加速、多供应商成本控制等需求的产生,互联网公司的多云部署和跨云迁移逐渐成为刚需,而在此过程中,最困扰运维和研发人员的就是数据的迁移和同步。俗语说“ 上屋搬下屋,搬洒一箩谷 ”,在业务的迁移过程中一旦遇到重要数据的丢失,将会对企业造成巨大的损失。

UCloud 通过对一些客户的跨云迁移过程进行总结,发现普遍存在的挑战有三点:

  • 数据完整性和一致性挑战。
  • 时效性,即迁移窗口期相对有限。
  • 应用依赖性和各种调用关系。

跨云迁移涉及到的资源主要分成三大类:第一类是 EIP、VPC、负载均衡和 NAT 网关这类网络服务,在跨云迁移的过程中这些都会发生变化,而且是无状态服务,配置并不复杂,对于这部分资源可以通过人工的方法对齐配置。

第二类是最为常见的云主机资源,这部分我们可以通过 UCloud 服务器迁移工具 USMC,以相同的配置在 UCloud 公有云上创建一份,只需保持和源端服务器 IP 一致的目标端服务器 IP,支持按分钟级别进行增量数据同步,减少业务切换的时间。

而第三类就是包括数据库、文件存储和对象存储在内的一些存储服务,我们可以通过 UDTS 数据传输工具进行迁移,而这一部分也正是本文重点讨论的实践内容。

通常,我们将跨云迁移划分为三个阶段: 数据同步阶段、数据规整阶段 (清理测试时产生的脏数据) 和数据割接阶段。数据同步阶段主要是需要解决两个问题,首先是将数据复制到新平台,并且让应用程序在新平台运行,这也是跨云迁移的核心;其次就是利用真实数据对应用程序进行测试,确认应用程序在目标平台可以符合预期地运行。

我们知道数据可以分为结构化数据和非结构化数据,用来存储数据的方法众多,接下来主要介绍数据同步阶段中常见的存储组件例如 MySQL、文件存储和对象存储的数据迁移实践。其它不同的存储组件各有不同,但也是可以参考这几个组件的迁移逻辑来处理的。

MySQL 同步

一般来说,我们认为对于 MySQL 的同步,只要存量数据和增量数据都能做到一致,那么整个数据库的同步就是一致的。而常见的 MySQL 数据迁移方式有两种:一种是基于 MySQL 主从的方式,通过 mysqldump 记录下 binlog 位置,然后把这个 binlog 位置前的数据完整导出,恢复出一个备库,然后再从记录的 binlog 位置开始向主库追平增量数据。

另一种就是 UDTS 工具,总体上也是分为存量阶段和增量阶段,增量阶段的追及是将从存量同步发起的一瞬间开始往后的数据变化通过 binlog 的形式同步到目标库。增量同步依靠 binlog 完成,这是 MySQL 主从同步的基础,是我们需要默认信任的数据一致性机制,当然我们最终需要以数据校验结果来确认数据是否一致。简而言之, 跨云迁移过程中 MySQL 的数据一致性主要就集中在存量数据的迁移如何保证一致。

【案例】

以近期的 xx 公司迁移到 UCloud 为例,其涉及数据库实例有数十个,并且由于应用依赖的原因需要进行整体迁移。在这案例中,如果采用 mysqldump 的方法,那么这数十个数据库都需要经过导出、传输、导入和配置主从这样的操作,给整个迁移任务增加了不少工作量。

同时也正如很多商业智能应用需要将数据汇总用作分析,这家公司的业务系统也有类似的汇总数据库,这种级联关系会让数据同步操作进一步复杂化。最终该公司使用了 UDTS 作为跨云数据同步的解决方案,在保障数据一致的同时,DBA 只需要提供两边数据库的连接和账号信息即可将数据同步任务托管,释放了运维人员的精力,专注去处理业务上的数据库工作需求。

数据同步

前面提到 MySQL 事务,在理解存量数据迁移过程中的数据一致性时,需要先了解 InnoDB 为代表的事务引擎和 MyISAM 代表的非事务引擎。使用 MyISAM 引擎的数据表确实没有很好的数据一致性确保手段,存量数据只能对数据表加读锁并迁移,在完成存量数据同步后,通过 binlog 追平,这样因为读锁会阻塞数据的写入,会导致业务的写入功能不可用,而且这一不可用的时间视表中数据体量而定。

然而因为 MyISAM 的不灵活,实际互联网公司中已经很少使用 MyISAM 引擎了。而 InnoDB 引擎因为它支持事务和行级锁的特性,在数据同步过程中对业务的影响小很多,但也因此对数据一致性的保护方法也相对复杂,而这一套一致性保护方法,核心就在于基于连接 session 的事务隔离和基于 MVCC 的数据版本管理,而 UDTS 也正是基于此而实现数据一致。

数据校验

数据一致性的关键,除了数据同步过程中的一致性保障,更加简单直接的手段是数据校验,只有对比过数据是一致的,那才是真正的一致。MySQL 数据校验的手段有很多,其中最经典的是 pt-table-checksum。

pt-table-checksum 会新建一个临时的 checksum 表,并且获取与主库有主从关系的所有从库信息。在校验工作时,工具会将该 session 的 binlog 格式设置为 statement,这样是为了利用 mysql 的 binlog 机制,将主库上执行的 sql 语句同步到从库去。接着工具会以 chunk 为单位从主库中读取数据和计算校验,将校验结果写入 checksum 表,这个过程会在一个语句中完成,随后这个语句由于对 checksum 表进行修改,会被同步到从库并且被从库执行。这样从库也会在自己的 checksum 表写入校验值。这个时候工具再从库中把 checksum 值读出,就可以与主库的计算值进行对比。

pt-table-checksum 的优势在于使用方便,在经历了多年迭代也有非常好的可靠性保证。但是它的技术限制也是明显,那就是要求被校验的两个库需要是主从关系,同时也要求数据表有索引,因为 chunk 大小的计算是通过索引完成的。

【案例】

以近期的 xx 公司迁移到 UCloud 为例,在数据同步的阶段由于数据库实例众多,需要减少 DBA 的工作负担而采用了 UDTS 来进行数据库迁移,但是这样就打破了源和目标库的主从关系,进而导致 pt-table-checksum 无法使用。当然实际上数据导出 - 传输 - 导入 - 配置主从这样的机械化操作可以通过制作脚本来解决,但是为了迁移而开发一套复用率不高的脚本代码并不明智。这时候 sync_diff_inspector 工具的优势就体现出来了。

sync_diff_inspector 是 TiDB 团队为了方便用户在 MySQL 数据迁移到 TiDB 后对数据一致性进行检查的开源工具,它不要求被校验的两个数据库存在主从关系,也没有对数据表索引的要求,甚至允许源库和目标库有不同的库名和表名,只要有明确的映射,就可以对数据本身进行校验。同时,在 sync_diff_inspector 发现某一块数据存在差异的时候,会通过二分对比的办法,最终找到实际不一致的行,缩小了疑似不一致的数据范围。

虽然这种相对松耦合的环境下对数据进行校验,可能会出现记录下一些数据不一致,例如主库的某个写入还没有完全即时的同步到从库,这时候进行检查可能会存在数据差异,但是除非源库 insert/delete/update 操作非常频繁,否则一般期望工具检查发现的差异不会太多。这时候只需要针对检查报告中的少数差异做第二次的手工或脚本校验,就可以确认数据一致性。当然如果一致性检查工具发现有较多数据不一致,一是可以用检查工具生成的一致性修复脚本来修复一致性,也可以对通过对数据进行重新同步来完成。

需要留意的是,pt-table-checksum 和 sync_diff_inspector 都是对实体数据进行校验的工具,在数据量较大的情况下校验操作会相对缓慢,不适合在割接时间窗口中操作。在实际项目中笔者测得一个 500G 的数据库的完整校验耗时大约 28 小时。在割接时间窗口中,一般通过 select max(id) 或者 select count(id) 对数据进行简单对比。

文件存储同步

文件同步

相比于 MySQL,文件作为一种非结构化的存储方式,迁移方法相对较少,也没有太多的数据一致性保障方法。与此同时,海量小文件的处理效率有限一直都是技术难题。

一般来说,文件存储的方式一般是硬盘本地存储或者基于 NFS 协议的存储服务,这两种存储服务中 NFS 存储的同步会更困难一些。单个文件的同步是简单的,将文件复制到目标空间然后再对文件计算 md5 校验和,只要两边的数据是一致的就行。难点在于获知文件是否有发生变化。在 linux kernel 中可以利用 inotify 机制了解到本机对文件的修改动作。

inotify 应用在启动的时候除了初始化监听和创建事件队列以外,还会在文件系统操作的函数中加入 inotify hook 函数以将文件系统事件通知到 inotify 系统中,这些都是操作系统内核中的系统调用。所以对于 NFS 而言 inotify 就失效了,因为相关调用都是本机环境中的系统调用而没有经过网络,挂载了同一个 NFS 的多台主机没有机制了解对方在什么时候对文件进行了操作。

所以这时候,从业务中对出现变化的文件进行记录就很有必要,因为实际上所有对文件的增、删、改都是业务所需的操作行为。所以在数据同步阶段,我们依然通过 rsync 或类似方法来同步数据,并且通过业务日志记录发生了变化的文件,最后在割接阶段解析业务日志,将出现过变化的文件做最后的增量同步,从而实现数据追平。

典型的组件可以参考 FastDFS,FastDFS 实现了类似 binlog 的方式,来记录每个 storaged 接受到哪些文件的更新,是哪种更新操作。在启动 storaged 之后,就可以实现自动读取其它同副本关系的 storaged 的数据来恢复。例如大 C 表示源创建,小 c 表示创建副本,大 A 表示源追加,小 a 标识副本追加,大 D 表示源删除,小 d 表示副本删除等等。

实际生产环境中的 fastdfs binlog

当然也有一些实现了分布式锁的文件系统,例如 vmware 的 vmfs 和 oracle 的 ocfs,可以共享文件系统数据的同时,通过锁机制来实现操作系统对文件变化的感知。

文件校验

文件的校验,这里会涉及到存储静默错误的问题。我们回忆硬盘坏道这个概念,就会发现硬盘自己也不知道某个扇区目前状态是否良好,需要专门进行扫描才能确认。一个扇区写了数据,在长久的运行中这一扇区成为了坏道导致不能读出数据,这时候应用不读取就不知道底层数据出现问题,这就是静默错误。

要解决静默错误的唯一办法是全链路数据校验:

  • 在数据上传前,确认数据正常,生成校验和;
  • 上传到某个存储服务之后,存储服务存储文件并且记录这个文件的校验和;
  • 定期对数据进行巡检,重新计算文件校验和并且和记录值比较;
  • 取出数据时,也对数据进行校验和比较,这样才能保证文件数据一致。

因此从技术层面来说建议从一开始就使用带有全链路数据校验功能的服务,自建存储服务的全链路一致性也需要自行建设,否则在迁移后只能通过 md5sum 这类工具对全部数据进行校验,确保迁移前后数据没有差异,而不保证迁移后的文件依然是访客当初上传的文件。尽管需要做这样的妥协,海量小文件的迁移和校验依然会造成迁移工期的压力。

利用 md5sum 递归遍历整个目录,生成所有文件的 md5 结果,可以通过以下命令完成:

find ./ -type f -print0 | xargs -0 md5sum > ./my.md5

相应的,可以通过以下命令对迁移后的整个目录进行递归遍历校验。

md5sum -c my.md5

对象存储同步

数据同步

对象存储的数据同步和校验的复杂度介于数据库和文件存储之间,因为它基本上是基于 HTTP 协议的,镜像回源的功能就能派上用场了,即如果一个文件在我们平台上不存在,那对象存储会尝试到源站去获取并保存下来。而相对于 InnoDB 数据表这种结构化数据,对象存储的数据一致性保障还是相对较弱。

目前市面上各种平台的对象存储服务对 S3 协议都有较好支持,而通过 US3SYNC 工具就可以将其他支持 S3 协议的对象存储数据迁移到 UCloud 对象存储 US3 中。虽然 US3 也支持镜像回源,但是在数据同步的刚开始时,不建议将原平台 bucket 配置为回源目标之后就将 US3 作为服务入口来使用起来,因为这个时候 US3 bucket 中还没有数据,直接使用 US3 会造成大量镜像回源,一是从而导致整体访问延迟变大,其次也容易出现访问失败的情况。

US3SYNC 工具与 redis 协同工作。在数据同步开始前,US3SYNC 工具会通过 S3 协议的列表接口,将一定数量的源 bucket 对象 key 以及这些 key 的同步状态记录进 redis 中。每当一个文件完成从源 bucket 的下载、缓存和上传到 US3 后,导入工具就会在 redis 中将数据标记为已同步。这样在 US3SYNC 工具因为一些可能的原因,例如网络环境不好等问题故障挂起之后,只需要重启 US3SYNC,它都可以从断点开始续传。

当完成一轮数据导入之后,就可以开始配置镜像回源配置了,这时候直接访问 US3 也能得到不错的命中率。当然也可以选择再运行一次 US3SYNC 工具,如果这样操作需要注意 US3SYNC 工具原本的功能是断点续传的,所以我们应该把 redis 的内容清除。

但是直接清理掉 redis 再重新跑,US3SYNC 工具的行为是重新加载文件列表并且重新写入 US3,这样会导致所有数据都要重新写一次,效率很低。在这个时候,我们可以配置 US3SYNC 工具为文件比对模式,在获取文件列表后将文件都通过 HEAD 获取文件大小,这时候只要将源 bucket HEAD 成功,但是 US3 为 not found 或者文件大小不同的数据同步到 US3 即可。在实际的数据迁移实践中,我们可以更加灵活的使用续传和比对模式来提高工作效率。

【案例】

以近期的 xx 公司迁移到 UCloud 为例,该公司的 CDN 和对象存储从友商迁移到 UCloud 的过程里面,有一个 bucket 中存在文件数量达到了 12 亿,将所有 key 存储到 redis 中并不合理,会导致 redis 数据膨胀,进而对迁移中转主机提出非常高的内存需求。这时候应该从一开始就配置 US3SYNC 工具为文件比对模式对数据进行迁移,进而避免不合理的 redis 内存使用。

数据校验

对象存储的数据校验方面,大多数对象存储都支持给文件提供 ETag 的 Header,且 ETag 的生成都跟原始数据有一定关系,所以可以根据源平台的 ETag 计算方式,在下载到文件后对文件进行一次计算,看看 ETag 是否相符。而 US3SYNC 功能本身也会按照 US3 的 ETag 计算规则预先计算我们的 ETag,在上传成功后对比 US3 返回的 ETag 和导入工具自行计算的值,来实现对数据的校验。

结尾

多云部署已成趋势,在帮助平台用户进行多云部署和数据迁移的过程中,UCloud 技术团队摸索和积累了丰富的实战经验。为了在有限的业务窗口期将海量数据进行迁移, UCloud 服务器迁移中心 USMC 和数据传输工具 UDTS,助力用户在保证数据完整性和一致性的前提下,大大提升了多云部署的数据同步效率。

由于篇幅限制,本文只对数据同步阶段中的存储组件 MySQL、文件存储和对象存储的数据迁移过程进行了解析,下一篇将介绍跨云迁移中数据规整阶段 (清理测试时产生的脏数据) 和数据割接阶段的实现细节。

声明:文章来源于 UCloud 技术,作者 iShadow。本平台只用于分享和交流不作商业用途,如侵权请及时联系我们删除。