Redis 双活架构

( 基于CRDTS )

双活(Active-Active)架构是一种数据弹性架构,它通过独立的、分布在不同地理位置的集群和节点,在多个数据中心分发数据库信息。该架构由多个独立的处理节点组成,每个节点都能访问共享的复制数据库,使所有节点都能参与同一应用,并确保本地低延迟,同时各区域可以独立运行。

双活架构或双活地理分布式拓扑(Active-Active Geo-Distributed topology)通过在 Redis企业版中实现 CRDT(无冲突复制数据类型) 来构建跨多个集群的全局数据库,这种数据库被称为无冲突复制数据库(CRDB)。

CRDB 相较于其他地理分布式解决方案,具备三大核心优势:

  • 本地低延迟:无论有多少个地理复制区域,或它们之间的距离多远,CRDB 都能确保读写操作的低延迟。
  • 无冲突数据同步:针对 Redis 核心的数据类型(包括简单和复杂类型),实现自动无冲突合并,避免数据不一致问题。

  • 高可用性保障:即使大部分地理复制区域(如 5 个区域中有 3 个宕机)无法运行,其余可用区域仍能继续执行读写操作,确保业务连续性。

部署与拓扑结构

CRDB(无冲突复制数据库) 是一个跨多个 Redis Enterprise 集群创建的数据库,这些集群通常位于全球不同的数据中心。每个参与集群中的数据库称为 “CRDB 实例”。只要 CRDB 数据集的大小适配 CRDB 实例的内存,每个 CRDB 实例都可以采用不同的配置——它们可以由不同数量的分片组成,并运行在不同数量或类型的集群节点上。

此外,一个 CRDB 实例可以部署在多可用区(AZ)的集群配置中,启用高可用性和数据持久化。而另一个 CRDB 实例则可能运行在单可用区的集群配置中,不启用高可用性或数据持久化。这些 CRDB 实例可以在任何云环境(包括 Google Cloud、Azure 和 AWS)中同时运行。这种灵活性有助于优化基础设施成本,并根据特定业务需求提升数据库性能。

使用 CRDB 的应用程序会连接到本地 CRDB 实例的端点。所有 CRDB 实例之间采用双向数据库复制,并形成网状拓扑结构,即应用程序对本地实例的所有写入都会被复制到所有其他实例,如下图所示:

高级架构

CRDB 架构基于 Redis 命令和数据类型的替代实现(即 无冲突复制数据类型,CRDT)。在 Redis 企业版中,CRDB 是基于 Redis 模块数据类型 API 构建的专有 Redis 模块

读取操作由本地 CRDB 实例处理。由于 CRDT 层采用无共识机制,无需进行其他双活架构常见的“读取修复”(read repair)。

写入操作遵循基于操作的 CRDT(operation-based CRDT) 原则,分为两个步骤:

  • 准备阶段:用户请求在本地 CRDB 实例上处理,并生成对应的影响数据(effect)
  • 应用阶段(Effect step):生成的影响数据会分发到所有 CRDB 实例(包括本地实例)并执行应用。

基于操作的 CRDT 要求影响数据必须确保精确投递一次(exactly-once delivery),并维持源 FIFO(先进先出)一致性。CRDB 主要依赖 Redis 复制机制,并针对该机制进行了一些优化,以满足这些一致性要求。

对等复制

CRDB 复制由 同步器(syncers) 实现,它们会与远程主实例(primary)建立连接并请求对等复制(Peer Replication),并为此引入了一种全新的数据库复制机制。对等复制的工作方式类似于标准 Redis 复制:

  • 通过 PSYNC 和 复制偏移量(replication offset) 来恢复中断的连接。
  • 在必要时,可回退至完整 SYNC 进行全量同步。

一旦对等节点建立复制连接,仅 由 CRDT 模块生成的 CRDT 影响数据(CRDT effects) 会被传播。根据不同的拓扑结构,还可以进行额外的筛选,以仅包含特定更新。在网状拓扑(mesh topology) 中,对等复制仅传播本地 CRDB 实例生成的影响数据。

此外,CRDB 实例可以向不同的对等实例推送多个复制流,使对等复制机制能够管理多个不同的复制日志。

自动 GZIP 压缩与 SSL 加密:当检测到远程公网 IP 时,CRDB 会自动启用 GZIP 压缩,以优化广域网(WAN)连接的带宽利用率。同时,如果在连接建立过程中检测到 SSL 握手,则会自动启用加密,确保数据传输的安全性。

下图展示了对等复制的运行机制:

高可用性与灾难恢复

如果一个或多个CRDB实例发生故障,全局 CRDB 下的其他实例仍可继续读写,确保业务持续可用,并提供灾难恢复能力。即使大部分 CRDB 实例(例如 5 个实例中有 3 个宕机)无法运行,剩余的 CRDB 实例仍能正常提供读写服务,不受影响。在此类区域性故障的情况下,无法连接到本地 CRDB 实例的用户通常会被自动引导至其他数据中心,以访问仍然可用的 CRDB 实例。这确保了即使本地 CRDB 实例宕机,应用的读写请求仍可正常进行。

在极少数情况下,某个 CRDB 实例可能会发生数据完全丢失,需要从头开始进行数据库复制。这种情况需要特殊处理,因为该 CRDB 实例在故障前可能已经向部分对等实例发送了更新,而这些更新的接收情况可能存在差异。由于无法保证所有对等实例最终会达成一致(部分影响数据可能仅被部分实例接收),Redis Enterprise 采用数据对账机制(Reconciliation Mechanism)来协调所有相关的 CRDB 实例。对账完成后,恢复中的实例可以从任意可用副本进行全量同步(Full Sync),以完成恢复。

一致性模型

在 CRDB 部署中,应用了多种一致性特性:

  • 本地 CRDB 实例操作:当使用 WAIT 命令时,可实现接近强一致性(near strong consistency);否则,操作被归类为弱一致性(weak consistency)。
  • 全局 CRDB 操作:采用强最终一致性(Strong Eventual Consistency, SEC),确保任何两个 CRDB 实例在接收到相同(无序)的更新集合后,无需共识协议即可达到相同状态。与仅提供活性保证(liveness guarantee)的最终一致性系统不同(即更新最终会被观察到),强最终一致性还额外提供安全性保证(safety guarantee),确保数据状态稳定可靠。

无冲突解析

CRDB 的冲突解析基于 CRDT(无冲突复制数据类型) 的三大原则:

  • 并发写入的结果是可预测的,并遵循一套确定性的规则。
  • 应用程序无需额外处理并发写入,但必须与 CRDB 的“方言”(dialect)兼容。

  • 数据集最终会收敛到单一、一致的状态,确保数据一致性。

冲突解析机制

每个 CRDB 实例都会独立维护一个向量时钟(vector clock),用于跟踪每个数据对象或子对象的更新状态。向量时钟会在本地更新操作发生时更新,或者当同一对象的更新操作从其他 CRDB 实例传入时更新。

当 CRDB 实例接收到来自其他实例的更新操作(及其向量时钟)时,会执行以下步骤:

第一阶段:更新分类

收到的更新操作可能属于以下三种情况:

  1. 新更新(new update)
  2. 旧更新(old update)

  3. 并发更新(concurrent update)

分类算法如下:

当实例 A 收到实例 B 发送的关于对象 X 的更新时:

  • 如果 x_vc[b] > x_vc[a],则该更新为 “新更新”。
  • 如果 x_vc[b] < x_vc[a],则该更新为 “旧更新”。

  • 如果 x_vc[b] ≸ x_vc[a],则该更新为 “并发更新”。

其中,x_vc[a] 是实例 A 维护的 对象 X 的向量时钟,x_vc[b] 是实例 B 维护的对象 X 的向量时钟。

第二阶段:本地更新对象

  • 如果更新被分类为“新更新”,则在本地 CRDB 实例中更新该对象的值。
  • 如果更新被分类为“旧更新”,则不在本地 CRDB 实例中更新对象的值。

  • 如果更新被分类为“并发更新”,则执行冲突解析机制,决定是否以及如何更新该对象的值。

CRDB 的冲突解析算法主要基于两个处理流程:

流程 1:无冲突数据类型/操作的解析

在许多并发更新场景下,某些数据类型的固有特性可确保更新完全无冲突(conflict-free),例如:

  • 计数器(Counter):当数据类型为 计数器(映射到 CRDT 的 Counter)时,所有操作都是可交换的(commutative),因此不会发生冲突。
    示例:用于跟踪文章热度、分享次数或不同地区的转发数量。
  • 集合(Set):当数据类型为 集合(映射到 CRDT 的 Add-Wins Observed-Removed Set),并且并发更新均为 ADD 操作 时,所有操作都是结合性(associative)或幂等的(idempotent),因此不会发生冲突。
    示例:在欺诈检测中,应用程序跟踪与特定 ID 或信用卡相关的可疑事件。如果关联的 Redis Set 基数达到某个阈值,则触发警报。

  • 哈希表(Hash):当数据类型为 哈希表(映射到 CRDT 的 map),并发更新发生在不同的 Hash 字段上时,所有操作都是无冲突的,相当于这些字段分别作用于不同的对象。
    示例:企业共享账户,多个用户在不同地点或以不同费率使用同一账户。每个账户的 HASH 对象包含多个字段,每个字段对应不同用户的使用情况,即使并发更新,也不会发生冲突。

在这些场景下,对象值的更新会按照数据类型的策略执行,确保数据的一致性。

流程 2:使用 “最后写入优先(LWW)” 机制解析冲突

对于非无冲突的数据类型(如 Redis 字符串(String),映射到 CRDT 的 register),需要应用冲突解析算法。在这种情况下,CRDB 采用 最后写入优先(LWW, Last Write Wins) 机制,通过操作时间戳(timestamp)作为仲裁标准来解析冲突。

即使不同地区的时间戳存在偏差(timestamp skew),LWW 仍可确保最终达到强最终一致性(Strong Eventual Consistency)。例如:

  • 假设实例 A 的时间戳总是比其他实例的时间戳靠前(即在发生冲突时,实例 A 总是获胜),最终仍能确保一致性。
    示例:当一个用户账户的密码被修改时,多个分布在不同地区的用户可能会受到影响。在这种情况下,其他用户将被强制登出,这可能是许可证管理等场景下的正确行为。

Redis企业版
将更多设想变为现实