CRAQ — 改进链复制
CRAQ 的全称是 Chain Replication with Apportioned Queries,是一种数据复制协议。其核心目标是 提升读吞吐 (read throughput),同时 保持强一致性。
背景 — 链复制 (Chain Replication)
- Chain Replication (链复制) 是一种“多个副本(replica) + 链状拓扑 (chain)”的数据复制方案:将所有服务器按顺序排列成一个链 (chain),最前的是 HEAD,最后的是 TAIL。
- 写 (write) 请求总是发给 HEAD。HEAD 更新自己的本地数据,然后将请求传给链上的下一个节点,依次类推,直到最终传到 TAIL。每个节点都按顺序执行写。写操作到达 TAIL 并执行后,TAIL 向客户端返回确认(commit)。
- 读 (read) 请求则由 TAIL 处理:客户端把读请求发送给 TAIL,TAIL 返回其当前状态 (最新已 commit 的数据) 。这样可以保证客户端读到的是最新写入(commit)后的状态。
- 优点:链复制保证强一致性 (strong consistency / linearizability) — 因为只有当写到达 TAIL 并提交后,才会被认为完成;所有读都从 TAIL 获取 —— 所以客户端永远只能看到已经 commit 的值。
- 写操作的开销较为平均 —— 与传统的主/备份 (primary‑backup) 复制相比,HEAD 只需发送给一个 successor 节点 (而不是同时广播给多个备份);链上的每个节点只负责转发给下一个节点,所以网络开销和负载分布较均匀。
- 缺点:链复制的一个主要问题是 读 (read) 的吞吐量 (throughput) 扩展性差 —— 所有读都必须送到 TAIL,所以即使有很多节点 (replica),读请求也仍然是单点 (TAIL) 处理,这会成为系统瓶颈。
CRAQ — 改进链复制:允许从任意副本读,同时保持强一致性
为了解决链复制读吞吐量瓶颈的问题,CRAQ (Chain Replication with Apportioned Queries) 应运而生。CRAQ 的设计目标是:在允许从任意副本读取 (read from any node) 的同时,仍保持强一致性 (linearizability / “last committed write” consistency)。
简单来说就是在 CR 的基础上,加上了分发(Queries) 和 询问(Queries)。
原先 CR 的缺点就是读必须读 TAIL,但这样长时间以后,链肯定会变长,越长就意味着越多的节点需要逐步 commit 才能到 TAIL,这样链式遍历的读取方法太慢了。
加上 AQ 之后,读请求就可以读取链上任意一个满足条件节点的数据了,这样就不需要遍历整条链到 TAIL 去获取数据,大大提高了读取效率。具体优化流程如下文所示。
下面是 CRAQ 的主要机制/流程:
- 在 CRAQ 中,每个对象 (object) 在每个 replica (链上的每个节点) 可能 同时存多个版本 (versions) —— 包括一个 “clean (干净)” 版本 (最近一次被 commit 的版本),以及 —— 如果最近有写操作正在向下传播 —— 一个或多个 “dirty (未 commit / 正在传播中)” 版本。
- 写请求 (write) 仍旧发送给 HEAD,HEAD 开始在链上向下传播写。每个节点在收到写请求时,不立即覆盖当前对象,而是 为该对象生成一个新的 dirty 版本 (带新的版本号),然后将写请求继续传给下一个节点。直到 TAIL。
- 当写传播到 TAIL,TAIL 把该版本标记为 clean (表示 commit),并向前传播一个 “ACK (确认)” 沿链返回给前面的节点 (HEAD 方向) —— ACK 中包含版本号。收到 ACK 后,各节点将其最新版本从 dirty → clean,并删除旧的历史版本 (即清理旧版本)。
对 读请求 (read):如果客户端对某个节点 (不一定是 TAIL) 发起读:
- 若该节点上的最新版本是 clean(即它知道最近一次 commit 后的值),它就可以直接返回这个 clean 版本 —— “clean read”。
- 如果该节点的最新版本是 dirty (说明该写还在传播 / commit 中),它 不能 直接返回这个 dirty 版本 (这可能违反一致性)。此时,该节点会向 TAIL 发起一个 “version query” —— 问 TAIL: “当前最新已 commit 的版本号是多少?” TAIL 返回版本号,然后节点根据这个版本号返回对应 clean 版本 (而不是返回 dirty 版本)。这样就保证读到的是最后已 commit 的版本。
- 因此,在大多数情况下 (read‑mostly workloads),大部分读会是 clean read (不需要联系 TAIL),所以读吞吐量可以随着链 (replica) 的数量线性扩展。(读重)
- 对写重 (write‑heavy) workloads 来说,可能许多读会碰上 dirty 状态,需要 version query —— 但 version query只是询问版本号 (metadata),比读取整个对象更轻量,因此相比传统链复制 (所有读都去 TAIL 读取整个对象) 压力也更小一些。
CRAQ 支持不同的一致性模型 (consistency models),适应不同应用需求/性能权衡:
- Strong Consistency(默认):保证所有 read 返回的是最后 commit 的写 (last committed write) 。
- Eventual Consistency(最终一致性):节点可以返回它拥有的最新版本 (dirty 或 clean),而不联系 TAIL。这种模式下,不同读可能看到不同版本 (可能旧) —— 适用于对实时性要求不高的场景。
- Eventual Consistency with Maximum‑Bounded Inconsistency:允许返回最近版本,但保证“最大不一致时间 / 版本号跨度”在设定的界限内。也就是说,你可能读到 stale,但 stale 的程度是受限的。适用于需要折中一致性和性能的场景。
CRAQ 的局限 / 风险 / 注意事项
虽然 CRAQ 解决了链复制读取扩展性的问题,但它也有一些缺点和注意事项:
- CRAQ 本身 不解决 链 (chain) 的 “谁是 HEAD / TAIL / 中间节点” 的配置管理 (chain membership / head-tail election / split-brain) —— 也就是说,当节点宕机 (crash) 或网络分区 (partition) 等,需要额外机制。这通常通过一个 配置管理器 (configuration manager) 来实现 (例如配合像 ZooKeeper) 去负责节点管理、head/tail 选举、处理节点加入/离开等。
- 由于写请求必须 经过链上所有节点 并等待 TAIL + ACK 返回 才 commit,所以如果链中有任何一个节点变慢 (slow)/性能差/网络不稳定,就会严重影响写操作性能 — 也就是说,写的延迟和吞吐容易被 “一颗慢节点 (slow node)” 拖累。相比像基于多数 (quorum) 的复制协议 (如 Raft / Paxos) 那种只需要多数节点响应即可 commit,CRAQ 在容错和性能方面更弱。
- 因为 CRAQ 不包含 “leader 选举 / partition 恢复 / split‑brain 检测” 的机制,所以它一般 需要和额外系统结合 (如 ZooKeeper 做 configuration management) 才能在实际系统中使用。
CRAQ vs Chain Replication vs Raft / 传统复制 (Primary‑Backup)
| 方案 | 写 (write) 路径 / commit | 读 (read) 路径 | 一致性 (consistency) | 读吞吐量扩展性 | 写延迟 / 故障敏感 |
|---|---|---|---|---|---|
| Chain Replication | 写 -> HEAD -> … -> TAIL -> commit | 所有读都到 TAIL | 强一致 (linearizability) | 读瓶颈 (单点 TAIL) | 写必须走全链,延迟 + 故障敏感 |
| CRAQ | 写 -> HEAD -> … -> TAIL -> commit (多版本机制) | 读可到任意副本 (clean read) 或 version‑query to TAIL | 强一致 (可选) / 或 eventual 等 | 读可扩展 (几乎和 replica 数线性) | 写必须走全链 + 等 ACK — 对慢节点敏感 |
| Raft / Primary‑Backup / Quorum-based | 写 -> leader -> 拍发多数备份 -> commit | 通常读要 leader 或最新同步过的 follower | 强一致性 (多数协议) | 读扩展 depends (需 follower) | 需要多数存活 + 对 leader 负载高 / 复制延迟 |
CRAQ 在读多写少 (read‑mostly) 的场景下特别有优势 — 它兼顾了读取扩展性和一致性。但如果写多 (write‑heavy) 或系统节点不稳定 (slow / fail),它的劣势就比较明显。
CRAQ 适用场景 & 设计取舍
考虑用 CRAQ 的场景包括:
- Read‑mostly workloads (读多写少):例如 key‑value store, 配置服务, 缓存系统, 不需要频繁写但有大量读请求。
- 需要强一致性 + 高读吞吐量:必须保证客户端总能读到最新 commit 数据,同时读请求量很大。
- Replica 数量多,希望读吞吐按 replica 数扩展:因为 CRAQ 中读可以分摊到所有副本 (clean read),读性能几乎和 replica 数线性关系。
但如果系统写很多、对写延迟敏感 (比如写重数据库)、或环境不稳定 (节点可能慢、宕机、分区),CRAQ 的局限性就要认真考虑。
问题
但正常来说,一个业务服务通常都需要读写兼容,写性能不能太差,读性能也不能太差,而且必须有较好的可用性,生产环境肯定希望的是即便出现故障的情况下,也能够保证业务几乎不受影响。
写性能与可用性问题
链上顺序写:CRAQ 的写请求必须从 HEAD 依次传到 TAIL,然后 TAIL 再向前回 ACK。
- 写延迟不可避免:如果链上任何一个节点慢或临时不可用,整个写操作都会被阻塞。
- 可用性受限:如果 HEAD 或 TAIL 宕机,链必须重新配置(选新 HEAD/Tail 并同步数据),在这段时间写请求会受阻。
相比之下,像 Raft 或 Paxos 这种 多数派提交(quorum-based)方案:
- 只要多数节点存活就可以 commit 写,不会因为单个节点慢而阻塞整个写操作。
- 写延迟可能略高,但在节点故障情况下可用性更高。
读写兼容与吞吐权衡
CRAQ 的优势在读多写少的场景:
- 读操作可以在多数节点上完成 “clean read”,吞吐几乎按副本数扩展。
- 写操作仍然需要全链顺序传播,所以写多的场景下性能会成为瓶颈。
现实业务中:
- 读写兼顾:像在线交易系统、电商后台、社交平台的消息存储系统,既要保证写延迟低,又要保证高并发读。
- CRAQ 在写多时可能会成为系统瓶颈,无法支撑高写并发。
容错与业务连续性
生产环境通常要求:
- 节点故障不会导致整个系统不可用。
- 数据必须保证一致性(或在可接受范围内 eventual consistency)。
- 系统可水平扩展。
CRAQ 本身:
- 没有自带 leader 选举或链配置管理,需要额外组件(如 ZooKeeper)保证链完整性和故障切换。
- 写依赖全链顺序提交,链中任何一个节点问题都会影响写可用性。
- 对慢节点非常敏感,即便只是一小段时间的性能抖动,也可能让写操作延迟剧增。
相比之下,多数派复制/Quorum 或 Raft:
- 写只要多数节点成功即可 commit,单点慢或故障不会影响系统整体可用性。
- 可以在 follower 上做可扩展读(如果允许读取可能稍旧数据),在生产环境中更灵活。
总结现实设计取舍
| 指标 | CRAQ 优势 | CRAQ 劣势 | Raft / Quorum 优势 |
|---|---|---|---|
| 读吞吐量 | 可扩展,尤其是读多场景 | 无 | 可通过 follower 提升,读可能略旧 |
| 写延迟 | 低延迟写到 HEAD 本地,但全链提交慢 | 写必须全链顺序传播,慢节点拖累 | 写只需多数派 commit,容错更好 |
| 可用性 | 读可在多数节点完成 | 写受链中节点影响,节点故障需重新配置 | 节点故障不影响多数派 commit,系统高可用 |
| 一致性 | 强一致性(linearizable) | 需要额外管理 chain 配置 | 强一致性(多数派 commit) |
| 适用场景 | 读多写少,追求低延迟读 | 写多或节点不稳定的系统 | 读写兼顾、生产环境、节点可能故障 |
现实生产环境通常会选择:
- 多数派协议(Raft / Paxos) + follower 读优化:在保证可用性和一致性的同时,读写性能平衡,系统健壮性更高。
- CRAQ / Chain Replication 可能用于读极多、写极少、对写延迟容忍高的场景,例如配置中心、缓存后端、日志存储等。
RoLingG | 博客
评论(0)