Cassandra 最终一致性取舍 — 可用性、延迟和新鲜度不能都拿满
待复核Cassandra 的最终一致性取舍,是在问一个很朴素的问题:当数据库被复制到很多机器上时,你愿意为了不停机和低延迟,暂时接受不同副本看起来不一样吗?
日常类比:像三家分店共用一本会员积分账。顾客在 A 店刚加完积分,B 店可能几秒后才收到消息。A 店先让顾客结账,保证队伍不卡住;代价是这几秒里,B 店看到的积分可能是旧的。
Cassandra 选择把这个取舍交给使用者:同一套系统里,你可以让一次写入等一个副本就返回,也可以等多数副本确认再返回。它不是单纯说“数据最终会一样”,而是把可用性、延迟、一致性新鲜度做成旋钮。
不理解这个取舍,下面这些事都很难解释:
- 为什么 Cassandra 在网络抖动或节点故障时还能继续接写入,而很多强一致数据库会先拒绝请求
- 为什么刚写完立刻读,有时读到旧值不是“数据丢了”,而是一致性级别选得太松
- 为什么
ONE、QUORUM、ALL这种读写级别会直接影响延迟和错误率 - 为什么同一个业务里,时间线点赞可以最终一致,但转账余额不能随便最终一致
- 为什么分布式数据库的难点不只是“多放几份”,而是“副本意见不一致时谁说了算”
Cassandra 的一致性取舍可以拆成 三件事:
-
多副本不是自动一致:一条数据通常写到 N 个副本。类比:同一份通知贴到三块公告板上,贴公告的人可能先贴完一块就被叫走,剩下两块晚一点才更新。
-
读写级别决定等待多少人点头:写入可以等 1 个副本、过半副本或全部副本确认;读取也可以问 1 个、过半或全部。类比:班级投票可以听一个代表,也可以等多数人举手。
-
后台修补让副本慢慢收敛: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时,读集合和写集合可能完全不重叠,所以没有人能保证读到新值
案例 2:把同一笔写入调成更稳
Section titled “案例 2:把同一笔写入调成更稳”N = 3W = 2 # 写入等多数副本R = 2 # 读取也问多数副本
if R + W > N: 读写集合必然至少重叠 1 个副本逐部分解释:
W=2会比W=1慢,因为要等两台机器回话R=2也会更慢,因为读取要联系更多副本R + W > N表示读写两边至少碰到同一个副本,新写入更不容易被读漏- 这不是免费的强一致魔法:网络分区时,等多数派可能让请求失败或变慢
案例 3:业务里怎么选档位
Section titled “案例 3:业务里怎么选档位”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 集群可以服务两种场景,但每个表、每类请求要知道自己能接受多大的“旧数据窗口”
-
把 eventual consistency 当成“最终一定很快一致”:后台修补需要时间,故障多或 repair 不跑时,旧值窗口会明显拉长。
-
只看写入成功,不看读取级别:
W=QUORUM但R=ONE,仍可能问到暂时落后的副本。 -
把
ALL当成最安全默认值:ALL一台副本不可达就失败,可用性会很差,只适合极少数必须全员确认的写入。 -
忽略冲突合并规则:同一列被不同副本写出不同版本时,Cassandra 依赖时间戳等规则决胜;机器时钟乱跳会制造难查的覆盖问题。
-
长期不做 repair:副本靠“最终”两个字不会自动永远健康,跨副本校验和修补是运维责任。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 写入量很大,允许几秒到几分钟的新鲜度延迟,比如日志、点击流、消息状态
- 跨机房或跨地域部署,可用性比单次读取的新鲜度更重要
- 业务能按请求选择一致性级别:关键读写用
QUORUM,统计类请求用ONE - 数据模型以主键查询为主,避免复杂事务和跨行强约束
不适用:
- 银行转账、库存扣减这类必须线性一致的核心账本
- 需要多行事务、外键约束、复杂 join 的关系型业务
- 数据量不大但团队没有分布式运维经验,单机 PostgreSQL 更稳
- 业务无法解释“短时间读到旧值”会造成什么后果
历史小故事(可跳过)
Section titled “历史小故事(可跳过)”- 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 策略陆续出现,说明“最终一致”只是起点,工程细节会继续补课。
- 一致性是旋钮,不是标签:同一个系统可以在不同请求上选择不同等待人数。
- 低延迟来自少等待:等的副本越少,请求越快,但读到旧值的窗口越大。
- 高可用来自不等所有人:节点坏了还能服务,前提是你接受之后再补齐。
- 最终一致需要运维兑现:hint、read repair、repair 这些机制不跑好,“最终”会变成“也许”。
- 业务先定义能错多久:技术选型前要先问,用户能否接受几秒、几分钟或永远不能接受的旧数据。
- 论文 PDF:Cassandra: A Decentralized Structured Storage System
- dynamo-2007 —— Cassandra 可用性优先和 quorum 思路的重要来源
- bigtable-2006 —— Cassandra 列族数据模型的另一半来源
- brewer-cap-2000 —— 网络分区下,一致性和可用性的取舍从这里讲起
- cassandra-2010 —— 同一系统的整体架构笔记
- 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 相关
(暂无反向链接)