跳转到内容

Cassandra 最终一致性取舍 — 可用性、延迟和新鲜度不能都拿满

待复核

Cassandra 的最终一致性取舍,是在问一个很朴素的问题:当数据库被复制到很多机器上时,你愿意为了不停机和低延迟,暂时接受不同副本看起来不一样吗?

日常类比:像三家分店共用一本会员积分账。顾客在 A 店刚加完积分,B 店可能几秒后才收到消息。A 店先让顾客结账,保证队伍不卡住;代价是这几秒里,B 店看到的积分可能是旧的。

Cassandra 选择把这个取舍交给使用者:同一套系统里,你可以让一次写入等一个副本就返回,也可以等多数副本确认再返回。它不是单纯说“数据最终会一样”,而是把可用性、延迟、一致性新鲜度做成旋钮。

不理解这个取舍,下面这些事都很难解释:

  • 为什么 Cassandra 在网络抖动或节点故障时还能继续接写入,而很多强一致数据库会先拒绝请求
  • 为什么刚写完立刻读,有时读到旧值不是“数据丢了”,而是一致性级别选得太松
  • 为什么 ONEQUORUMALL 这种读写级别会直接影响延迟和错误率
  • 为什么同一个业务里,时间线点赞可以最终一致,但转账余额不能随便最终一致
  • 为什么分布式数据库的难点不只是“多放几份”,而是“副本意见不一致时谁说了算”

Cassandra 的一致性取舍可以拆成 三件事

  1. 多副本不是自动一致:一条数据通常写到 N 个副本。类比:同一份通知贴到三块公告板上,贴公告的人可能先贴完一块就被叫走,剩下两块晚一点才更新。

  2. 读写级别决定等待多少人点头:写入可以等 1 个副本、过半副本或全部副本确认;读取也可以问 1 个、过半或全部。类比:班级投票可以听一个代表,也可以等多数人举手。

  3. 后台修补让副本慢慢收敛:Cassandra 用 hinted handoff、read repair、anti-entropy repair 等机制补齐漏掉的更新。类比:分店每天打烊后对账,把白天没同步上的账补回来。

这三件事合起来,就是“先让系统活着,再用规则控制错误窗口”。

案例 1:用 N、R、W 看懂可调一致性

Section titled “案例 1:用 N、R、W 看懂可调一致性”
N = 3 # 每条数据存 3 个副本
W = 1 # 写入等 1 个副本确认
R = 1 # 读取问 1 个副本
if R + W <= N:
可能读到旧值

逐部分解释:

  • N=3 表示数据有三份,不是只有一台机器说了算
  • W=1 表示写入很快返回,但另外两个副本可能还没收到
  • R=1 表示读取也很快,但刚好问到旧副本时就会看到旧值
  • R + W <= N 时,读集合和写集合可能完全不重叠,所以没有人能保证读到新值
N = 3
W = 2 # 写入等多数副本
R = 2 # 读取也问多数副本
if R + W > N:
读写集合必然至少重叠 1 个副本

逐部分解释:

  • W=2 会比 W=1 慢,因为要等两台机器回话
  • R=2 也会更慢,因为读取要联系更多副本
  • R + W > N 表示读写两边至少碰到同一个副本,新写入更不容易被读漏
  • 这不是免费的强一致魔法:网络分区时,等多数派可能让请求失败或变慢
write_profile("user:42", data, consistency="QUORUM")
read_profile("user:42", consistency="QUORUM")
write_page_view("article:9", event, consistency="ONE")
read_page_view_count("article:9", consistency="ONE")

逐部分解释:

  • 用户资料写错会直接影响体验,所以读写都选 QUORUM
  • 浏览量统计可以晚几秒合并,选 ONE 换低延迟和高可用
  • 同一套 Cassandra 集群可以服务两种场景,但每个表、每类请求要知道自己能接受多大的“旧数据窗口”
  1. 把 eventual consistency 当成“最终一定很快一致”:后台修补需要时间,故障多或 repair 不跑时,旧值窗口会明显拉长。

  2. 只看写入成功,不看读取级别W=QUORUMR=ONE,仍可能问到暂时落后的副本。

  3. ALL 当成最安全默认值ALL 一台副本不可达就失败,可用性会很差,只适合极少数必须全员确认的写入。

  4. 忽略冲突合并规则:同一列被不同副本写出不同版本时,Cassandra 依赖时间戳等规则决胜;机器时钟乱跳会制造难查的覆盖问题。

  5. 长期不做 repair:副本靠“最终”两个字不会自动永远健康,跨副本校验和修补是运维责任。

适用

  • 写入量很大,允许几秒到几分钟的新鲜度延迟,比如日志、点击流、消息状态
  • 跨机房或跨地域部署,可用性比单次读取的新鲜度更重要
  • 业务能按请求选择一致性级别:关键读写用 QUORUM,统计类请求用 ONE
  • 数据模型以主键查询为主,避免复杂事务和跨行强约束

不适用

  • 银行转账、库存扣减这类必须线性一致的核心账本
  • 需要多行事务、外键约束、复杂 join 的关系型业务
  • 数据量不大但团队没有分布式运维经验,单机 PostgreSQL 更稳
  • 业务无法解释“短时间读到旧值”会造成什么后果
  • 2007 年:Amazon Dynamo 论文把“可用性优先、多副本、最终一致”讲成一套完整设计。
  • 2008 年:Facebook 为 Inbox Search 造 Cassandra,借了 Dynamo 的去中心化骨架和 Bigtable 的列族模型。
  • 2009 年:Lakshman 和 Malik 在 LADIS workshop 发表 Cassandra 短文,强调无中心节点、可调一致性和高写入吞吐。
  • 2010 年:论文扩展发表在 SIGOPS Operating Systems Review;同年 Cassandra 成为 Apache 顶级项目。
  • 后来:CQL、vnode、轻量级事务、不同 compaction 策略陆续出现,说明“最终一致”只是起点,工程细节会继续补课。
  1. 一致性是旋钮,不是标签:同一个系统可以在不同请求上选择不同等待人数。
  2. 低延迟来自少等待:等的副本越少,请求越快,但读到旧值的窗口越大。
  3. 高可用来自不等所有人:节点坏了还能服务,前提是你接受之后再补齐。
  4. 最终一致需要运维兑现:hint、read repair、repair 这些机制不跑好,“最终”会变成“也许”。
  5. 业务先定义能错多久:技术选型前要先问,用户能否接受几秒、几分钟或永远不能接受的旧数据。
  • cassandra-2010 —— 从系统全貌看一致性、存储引擎和 gossip 如何配合
  • dynamo-2007 —— 用 sloppy quorum 和 hinted handoff 解释“先写下再补账”
  • bigtable-2006 —— 列族、memtable、SSTable 是 Cassandra 数据模型和存储层的来源
  • brewer-cap-2000 —— Cassandra 的选择正是 CAP 取舍的工程版本
  • paxos-1998 —— 如果业务需要强仲裁,就会走向共识协议而不是单纯最终一致
  • rocksdb-lsm —— 写入先落日志和内存、后台合并的存储思路与 LSM-tree 相关

(暂无反向链接)