多主数据库使用向量时钟同步

对于数据库的每条记录,每个系统都保存自己的内部逻辑时钟以及从对齐队列收到的另一个数据库的时钟。

假设有两个主数据库:Master A 和Master B,都有由Clock A 和Clock B列。

  1. Master A修改了一条记录,并增加了自己的Clock A的值。
  2. Master B接收该记录并比较两个时钟。
  3. 如果Clock B为 0,且相等,而Clock A已增加,Master B接受该消息并通过将其与Master A的记录对齐来覆盖自己的记录。
  4. Master B对同一条记录执行类似的修改,增加自己的时钟Clock B。
  5. Master A将接收该消息,由于Clock A相同,因此它可以通过对齐记录来接受该消息。 

当两个系统同时对同一记录进行修改时,就有可能发生冲突。在这种特殊情况下,两个系统都会收到一个对齐信息,其中各自的时钟相对于当时存储的内容都是次要的。虽然这种情况很少发生,但我们仍需要确定如何解决冲突。

可能会有不同的解决方案:

  • 例如,我们可以决定在发生冲突时,两个主站中的一个总是胜出,这意味着它比另一个 "more master"。
  • 或者,就像我们决定的那样,使用时间戳来定义 "last "记录。
我们知道,使用时间戳来定义排序可能会有很大的问题,但发生冲突(即两个系统在短时间内对相同数据进行更新)的概率被认为非常低(低于 0.1%)。 在这种情况下,对齐信息中也必须发送事件时间戳。

背景:
我们处理的场景是应用程序的技术迁移,公司几乎所有流程都依赖于该应用程序。主要业务限制之一 是旧应用程序不会在开发结束时退役,而是会与新应用程序长期共存,从而允许所有流程逐步迁移到新版本。

这一事实的结果是,这两个数据库都将成为主数据库,并且需要保持一致。  
以下是影响我们做出决定的主要技术限制因素列表:

  1. 这两个数据库处理相同的数据集,但具有不同的模式:例如,一个数据库上的客户使用与另一个数据库不同的表和列来表示。
  2. 没有可用于同步数据库的CDC(变更数据捕获)产品。
  3. 遗留应用程序只能通过异步消息来同步自身。
  4. 如果两个应用程序中有一个出现故障,另一个必须仍然可用。
我们通过以下决策来寻求解决方案:
  1. 我们决定使用应用程序级别管理的双向异步消息通信在两个主服务器之间交换数据,并在双方实现相同的同步算法。
  2. 每个主控都会发布一个对齐事件,该事件携带与上次修改对齐的整套数据。 
  3. 我们利用矢量时钟算法 来处理双方的事件。

异步通信及常用算法
两个Kafka队列用于双向交换消息。两个队列上的Avro模式保持相同,因此事件的格式也相同。 

这样的决定使我们能够创建一个与所使用的技术无关的两个主控共同的抽象层,但它仅依赖于对齐算法和用于事件的共享数据模型。
我们希望重点关注的主要优势是:

  1. 将对齐模块与两个主控的实现分开,这样就可以分别处理设计。
  2. 允许两个主服务器独立工作。如果一个主服务器停止工作,另一个可以继续。
  3. 将所有事情都依赖算法意味着不依赖特定技术,而只依赖其实现,这可以通过特殊的测试套件进行测试。从长远来看,这将带来稳定的解决方案,并且不易出错。
需要付出的代价就是在两个应用程序上复制该算法。 

建立消息之间的顺序
对齐数据库的一个关键要求是一种机制,该机制能够对消息进行排序,而不管消息是在哪个系统中生成的。这种排序机制对于维护分布式环境中数据的完整性和一致性至关重要。排序有两种类型:完全排序和部分排序。完全排序允许按顺序排列所有生成的消息,从而提供整个系统中事件的全面视图。另一方面,部分排序仅有助于按顺序排列一部分消息,从而为事件的关联方式提供了灵活性。
我们评估了实现消息间顺序的不同解决方案:

1、服务器时钟
使用服务器时钟作为排序依据可能很简单,但会引发使用哪个服务器时钟的问题。每个应用程序都有自己的基础架构和组件。哪些组件用作时钟的参考?如何保持它们同步?如果不一致,确定行动方案就变得至关重要,顺序可能会受到影响。

2、专用的集中式逻辑时钟
集中式逻辑时钟提供了一种替代方案,它为整个系统提供了单一的时间参考点。但是,这种集中式可能会带来瓶颈和故障点,因此对于高度分布式或可扩展的系统来说,它不太理想。

3、分布式逻辑时钟
分布式逻辑时钟(例如矢量时钟)提供了一种解决方案,允许全序和部分排序,而无需依赖单点故障。这种方法使系统的每个部分都能维护自己的时钟,并建立机制根据新消息的到达或数据更改来更新这些时钟。矢量时钟特别适合管理分布式系统的复杂性,提供了一种解决冲突和有效同步数据的方法。