数据迁移
数据迁移
为了最大限度地减少服务拆分与分库分表给业务带来的影响(不影响业务开发也是架构转型的前提),我们采用了一种温和的渐进式拆分方案:
- 对于每块需要拆分的领域,首先拆分出子服务,并将所有该领域的数据库操作封装为 RPC 接口;
- 将其它所有服务中对该领域数据表的操作替换为 RPC 调用;
- 拆分该领域的数据表,使用数据同步保证旧库中的表与新表数据一致;
- 将该子服务中的数据库操作逐步迁移到新表,分批上线;
- 全部迁移完成后,切断同步,该服务拆分结束。
这种方案能够做到平滑迁移,但其中却有几个棘手的问题:
- 旧表新表的数据一致性如何保证?
- 如何支持异构迁移?(由于旧表的设计往往非常范式化,因此拆分后的新表会增加很多来自其它表的冗余列)
- 如何保证数据同步的实时性?(往往会先迁移读操作到新表,这时就要求旧表的写操作必须准实时地同步到新表)
典型的解决方案有两种:
-
双写(dual write): 即所有写入操作同时写入旧表和新表,这种方式可以完全控制应用代码如何写数据库,听上去简单明了。但它会引入复杂的分布式一致性问题:要保证新旧库中两张表数据一致,双写操作就必须在一个分布式事务中完成,而分布式事务的代价太高了。
-
数据变更抓取(change data capture, CDC): 通过数据源的事务日志抓取数据源变更,这能解决一致性问题(只要下游能保证变更应用到新库上)。它的问题在于各种数据源的变更抓取没有统一的协议,如 MySQL 用 Binlog,PostgreSQL 用 Logical decoding 机制,MongoDB 里则是 oplog。
最终我们选择使用数据变更抓取实现数据同步与迁移,一是因为数据一致性的优先级更高,二是因为开源社区的多种组件能够帮助我们解决没有统一协议带来的 CDC 模块开发困难的问题。只有一个 CDC 模块当然是不够的,因为下游的消费者不可能随时就位等待 CDC 模块的推送。因此我们还需要引入一个变更分发平台,它的作用是:
- 提供变更数据的堆积能力;
- 支持多个下游消费者按不同速度消费;
- 解耦 CDC 模块与消费者;
这也就是一套实时数据管道,设计目标是通过 CDC 模块抓取业务数据源变更,并以统一的格式发布到变更分发平台,所有消费者通过客户端库接入变更分发平台获取实时数据变更。
-
一致性:数据变更分发给下游应用后,下游应用可以不断重试保证变更成功应用到目标数据源——这个过程要真正实现一致性还要满足两个前提,一是从数据变更抓取模块投递到下游应用并消费这个过程不能丢数据,也就是要保证至少一次交付;二是下游应用的消费必须是幂等的。
-
异构迁移:异构包含多种含义:表的 Schema 不同、表的物理结构不同(单表到分片表)、数据库不同(如 MySQL -> EleasticSearch),后两者只要下游消费端实现对应的写入接口就能解决;而 Schema 不同,尤其是当新库的表聚合了多张旧库的表信息时,就要用反查源数据库或 Stream Join 等手段实现。
-
实时性:只要保证各模块的数据传输与写入的效率,该模型便能保证实时性。