Live data migration

出于业务升级,数据库更换选型,数据库大版本升级等业务或技术的各种原因。可能会有将数据从数据源A迁移至数据源B的需求。为确保业务稳定,迁移过程需要平稳、安全,并且保证双边数据一致性。

静态数据

对于稳定不变的只读性数据,分为简单的两步即可:

  1. 将存量数据直接从数据源A导致入数据源B
  2. 切换业务路由至数据源B

上面两步均不存在不一致情况。

动态数据

对于同时读写的动态数据,一般分成三步:

  1. 增量迁移,将新增的数据直接写入数据源B
  2. 存量迁移,将存量数据迁移至数据源B
  3. 切换业务路由至数据源B

每步可以通过不同的技术手段达到目的,但临界或异常时可能会出现数据分叉的情况,需要额外的技术手段消除。

双读切写模式

读一致性

通过双读比较数据的版本,以较高者为准。

切写的原子性

由于分布式机器中存在通信延迟的情况,上图第二步切换写路由的动作无法在多个DAO机器中原子性完成,会出现部分DAO机器已切写至target db,但有部分DAO机器仍会写向source db的临界情况。

在T0~T1期间出现并发请求,导致对于同一个id的记录,在两个数据库中版本一致但内部不一致,且都对上层业务返回了成功,出现了数据分叉。

  • 对于并发情况较多或对一致性要求较高的场景,切写不一致的解决方法:

    1. 在T0~T1期间,只读不写,按经验T1-T0>DAO超时时间 * 4

    2. 按用户或商户维度分批择时切写,避免全量不可写

  • 对于并发情况较少或容忍少量非一致性的场景,可以开放T0~T1期间的写请求,采用事后处理的策略:

    1. 双读时发现不一致性情况,采用信任某一数据源策略,或直接报错并告警,人工干预处理

    2. 准实时对账两个数据库,发现数据不一致性后告警,人工干预处理

同步双写模式

同步双写一般不是一个原子操作,也就会出现source db写成功,target db写失败的异常情况,这会引发一些值得探讨的问题:

应向业务返回成功还是失败?

先假设返回成功,考虑以下场景:

  1. 假设有一条 Create 请求: X (key=hanbo, addr=sz, version=0),写source db成功,写target db失败。此时返回成功,此条数据X已经不一致,即source db有数据X,而target db无数据X。
  2. 上图第一步双写完成,进入第二步迁移存量数据,会将数据X迁移至target db。数据X达成一致,符合预期
  3. 迁移完成后,有另一条 Create 请求:Y (key=bobo, addr=xa, version=0),写source db成功,写target db失败。此时返回成功,但迁移已经完成,数据源B不会再有机会迁移数据Y,等于这条数据就丢失了,不符合预期

因此此种异常不应返回成功,应向业务返回失败

如何保证读一致性?即读时应以哪个数据源为准?

假设读以source db为准,上述异常时,向业务返回了失败。此时业务查询又返回了数据,前后不一致,不符合预期

又对于存量数据,由于只存在于source db,读必须以source db为准

因此应将读分为两类:

  • 双写数据的读应以target db为准
  • 存量数据应以source db为准

哪些写请求应该双写?

写请求可分类为:

  • Create 请求

  • Update 请求

  • 双写数据(Create双写产生)的Update

  • 存量数据的Update

Create 请求是新增数据,应 双写

双写数据的Update请求,由于双写数据的读以数据源B为准,所以必须双写

存量数据的Update请求,先假设双写,考虑以下场景:

  1. 已有存量数据X(key=tencent, addr=sz, version=1),即数据源A有数据X,数据源B无数据X
  2. 此时有Update请求 set addr=sh where key=tencent 。双写时写数据源A成功,写数据源B失败,向业务返回失败。此时数据源A有X(key=tencent, addr=sh, version=2),数据源B无X
  3. 业务查询数据X(预期应该是addr=sz),若读数据源A会返回addr=sh,前后逻辑不一致,不符合预期;若读数据源B会返回空,不符合预期

因此存量数据的Update请求不应双写。

如何处理这种异常导致的不一致?

这种异常一般不影响业务的读写一致性,可以不用处理。

但对于迁移的回滚产生不一致影响,考虑以下场景:

  1. 假设有一条 Create 请求: X (key=tencent, addr=sz, version=0),写数据源A成功,写数据源B失败。向业务返回失败,此条数据X已经不一致,即数据源A有数据X,而数据源B无数据X。
  2. 由于某种故障或其他原因,需要回滚迁移,即结束迁移流程,业务读写都路由至数据源A。
  3. 务查询数据X(预期应该是返回空),但却返回(key=tencent, addr=sz, version=0),不符合预期

这种回滚导致的前后不一致,对于资金转账类场景完全不可接受(比如转账失败,最后却多一条转账成功的记录),只能一条路走到黑。

存量数据迁移的一致性

存量数据的写请求路由至数据源A,存量数据迁移时写向数据源B,若两者同时出现,会出现数据不一致情况,考虑以下场景:

在T0~T1期间,若有写请求修改了数据源A,则会导致数据源A比数据源B更新。在迁移完成后,数据成为双写数据,读以数据源B为准,会彻底丢失了这次业务的成功更新。

针对迁移中的不一致解决办法:

  1. 在T0~T1的迁移过程中,只读不写
  2. 按用户、商户或其他维度分批逐渐迁移,避免全量不可写

reference

https://zhuanlan.zhihu.com/p/455976887