主动-主动架构是一种数据弹性架构,通过独立且地理上分布的集群和节点,将数据库信息分布在多个数据中心。它是一个由独立的处理节点组成的网络,每个节点都可以访问一个共同复制的数据库,从而使所有节点都能够参与一个共同的应用程序,确保每个区域都能够独立运行,从而实现本地低延迟。
通过在 Redis 企业版中使用跨越多个集群的全局数据库实现 CRDT (无冲突复制数据类型),可以实现主动-主动架构或主动-主动地理分布拓扑结构。这被称为“无冲突复制数据库”或“CRDB”。
与其他地理分布式解决方案相比,CRDB 提供了三个主要优势
CRDB 是一个跨多个 Redis 企业版集群创建的数据库,这些集群通常位于世界各地的不同数据中心。参与的每个集群中的数据库被称为“CRDB 实例”。只要 CRDB 数据集适合 CRDB 实例内存,每个 CRDB 实例都可以通过不同的方式配置,即由不同数量的碎片组成,并在不同数量或类型的集群节点上运行
此外,一个 CRDB 实例可以在具有高可用性和数据持久性的多可用区 (AZ) 集群配置上运行。第二个 CRDB 实例可以在没有高可用性或数据持久性的单个 AZ 集群配置上运行。这些 CRDB 可以同时在任何云环境中运行,包括 Google Cloud、Azure 和 AWS。这种增加的灵活性优化了基础设施成本和数据库性能,以满足您的特定用例。
使用 CRDB 的应用程序连接到本地 CRDB 实例端点。所有 CRDB 实例之间使用双向数据库复制,以网格状拓扑结构进行,即应用程序对本地实例的所有写入都会复制到所有其他实例,如下所示
CRDB 架构基于大多数 Redis 命令和数据类型的替代实现(如上所述,称为无冲突复制数据类型)。在 Redis 企业版中,我们的 CRDB 实现基于使用 Redis 模块数据类型 API 构建的专有 Redis 模块。
读取命令使用本地 CRDB 实例本地处理。CRDT 层的固有无共识机制不需要其他主动-主动实现中常见的“读取修复”。
写入 命令按两步处理,遵循基于操作的 CRDT 的原则
基于操作的 CRDT 需要将效果更新以保证一次且仅一次地传递到所有 CRDB 实例,并以源 FIFO 一致性进行传递。CRDB 通常依赖于 Redis 复制机制,并进行了一些修改以满足这些保证。
CRDB 复制通过同步器实现,同步器联系远程主节点并请求对等复制(如下所示),并引入了专门为此目的而设计的新数据库复制机制。对等复制与标准 Redis 复制类似
对等节点建立复制链接后,只传播 CRDT 模块生成的 CRDT 效果。可以应用额外的过滤,以根据拓扑结构仅包含特定更新。当使用网格拓扑结构时,对等复制将只携带在本地 CRDB 实例上生成的效应。
CRDB 实例可以将不同的复制流推送到其他对等节点,以便对等复制机制可以管理许多不同的复制积压。
当识别出远程公共 IP 时,GZIP 压缩会自动应用于对等复制,以更好地利用广域网链接。此外,如果在连接建立期间识别出 SSL 握手,则会自动设置加密。
对等复制操作说明如下
如果一个或多个 CRDB 实例发生故障,全局 CRDB 下的其他实例将继续读取和写入,提供持续的可用性和灾难恢复。即使大多数 CRDB 实例(例如,5 个中的 3 个)都处于停机状态,剩余的 CRDB 实例也不会中断,可以继续进行读取和写入。在这些区域性故障情况下,无法连接到本地 CRDB 实例的用户通常会被重定向到指向可用 CRDB 实例之一的另一个数据中心。这为应用程序的读取和写入提供了持续可用性,即使用户的本地 CRDB 实例处于停机状态。
在极少数情况下,CRDB 实例可能会遇到完全的数据丢失,需要从头开始进行数据库复制。这种情况需要特殊处理,因为恢复的 CRDB 实例可能已将更新发送到其一些对等节点。由于无法预期进一步的更新,因此我们无法假设所有对等节点最终都会收敛(一些效应消息可能已被一些对等节点接收,但其他对等节点没有收到)。在这种情况下,Redis 企业版实施了一个协调机制,涉及所有相关的 CRDB 实例。协调完成后,恢复的实例只需从任何其他副本进行完全同步。
在 CRDB 部署中应用了多种一致性特征
CRDB 冲突解决基于三个 CRDT 原则
每个 CRDB 实例都单独维护一个 向量时钟,用于每个数据集对象/子对象。在实例级别进行任何更新操作或从其他 CRDB 实例接收到同一个对象的另一个更新操作时,都会更新此向量时钟。
从另一个实例接收到更新操作(和向量时钟)后,每个 CRDB 实例都会单独执行以下过程
阶段 1: 对更新操作进行分类
接收到的更新操作可以表示 (1) 新更新、(2) 旧更新或 (3) 并发更新。
分类算法的工作原理如下
当实例 A 从实例 B 接收有关对象 X 的更新时
其中 x_vc[a] 是实例 A 中对象 X 的向量时钟,x_vc[b] 是实例 B 中对象 X 的向量时钟。
阶段 2: 在本地更新对象
CRDB 冲突解决算法基于两个主要过程
过程 1:针对无冲突数据类型/操作的冲突解决
在许多并发更新状态情况下,可以根据适用数据类型的属性,完全无冲突地处理更新。以下是一些示例
在所有这些情况下,本实例中的对象值根据数据类型策略进行更新。
**过程 2**:使用最后写入者获胜 (LWW) 机制解决冲突
在非无冲突数据类型(如 Redis 字符串(映射到 CRDT 的寄存器))中出现并发更新的情况下,应该应用冲突解决算法。我们使用 LWW 方法来解决此类情况,方法是利用操作时间戳作为决胜局。
请注意,我们的解决方案以强最终一致的方式工作,即使区域之间存在时间戳偏差。例如,假设实例 A 的时间戳始终领先于其他实例的时间戳(即,在决胜局中,实例 A 始终获胜)。这确保了最终一致的行为。**示例:**为由多个地理分布式实体访问的用户帐户更改密码。在这种情况下,更改将使其他用户注销,这可能是许可证执行场景的正确行为
**数据类型:** 字符串
**用例:** 非并发 SET
**冲突解决:** 无冲突
注意:无冲突,key1 在 t5 时最后设置为“value3”。
**数据类型:** 字符串
**用例:** 并发 SET
**冲突解决:** 最后写入者获胜 (LWW)
注意:t2>t1 并且由于 LWW,key1 设置为“value2”。
**数据类型:** 字符串
**用例:** APPEND 与 DEL
**冲突解决:** 添加获胜
注意:APPEND 是一个“更新”操作,被视为“添加”,因此比 DEL 操作更胜一筹。
**数据类型:** 字符串
**用例:** 并发过期
**冲突解决:** 更大的 TTL 获胜
注意:在 t6 时,实例 B 持久化(使用 PERSIST key1),这意味着它的 TTL 设置为无穷大(即 -1),大于 实例 A 设置的 100。
**数据类型:** 计数器
**用例:** 并发增量/减量操作
**冲突解决**:无冲突
注意:在 CRDT 层,计数器值是所有操作的总和。
**数据类型:** 计数器
**用例:** 并发删除和增量操作(在计数器值中观察到的删除)
**冲突解决:** 添加获胜
注意:INCRBY 是一个“更新”操作,被视为“添加”,因此比 DEL 操作更胜一筹。此外,实例 A 在 t3 时执行的计数器删除操作在逻辑上意味着重置计数器。
**数据类型:** 集合
**用例:** 对集合执行并发 SADD 操作
**冲突解决:** 无冲突
注意:此示例是无冲突的,因为 SADD 是一个结合操作。
**数据类型:** 集合
**用例:** 对集合执行并发 SADD 和 SREM 操作
**冲突解决:** 添加获胜
注意:在集合元素中,添加获胜。
**数据类型:** 集合
**用例:** 对集合执行并发复杂操作(观察到的删除)
**冲突解决:** 在集合元素中,添加获胜
注意:在 t5 时,实例 A 只能从 key1 中删除元素 A 和 B。
**数据类型:** 发布/订阅
**用例:** 发布的消息传播到所有 CRDB 实例
**冲突解决:** 无冲突
注意:在PUBLISH 响应中,只计算本地订阅者。
**数据类型:** 字符串
**用例:** 垃圾回收
**冲突解决:** 无冲突
注意:CRDT 广泛使用墓碑。墓碑在被所有实例观察到后会变成垃圾。
下一节 ► 自动分层