想象你走进一座未来主义图书馆。这里的书架宽得惊人——同一排书架上,每本书可以有完全不同的页数和结构,甚至新书可以随时改变自己的“格式”。书架的排列不是直线,而是一个巨大的圆环,新书通过“分类编号”自动落位,同一本书会有三份副本分散在不同书库。当你查询时,可以选择“随便看看”(只读一份副本)或“仔细核对”(核对多份副本)。这座图书馆的一切设计,都围绕一个目标:让海量书籍的写入更轻松,让每种查询都有直达路径。它就是 Apache Cassandra——一个以宽列模型著称、为海量写入而生的分布式 NoSQL 数据库。本文从宽列模型讲起,探索分区键如何决定数据分布、聚簇列如何维持分区内秩序、一致性哈希环如何弹性扩展,再到复制因子与可调一致性的权衡,最后学习查询驱动建模与反规范化实践,用一座“环形图书馆”的运转逻辑帮你建立完整直觉。

岗位关联

对于 AI 从业者来说,理解 Cassandra 数据模型意味着能在大规模数据管道的底层存储选型中做出精准判断,也能在设计 AI 应用的数据特征层时,避开 JOIN 与热点写入的陷阱,产出真正可支撑海量业务的工程方案。

目录
  1. 可视化演示
  2. 宽列模型基础
  3. 分区键设计
  4. 聚簇列排序
  5. 一致性哈希
  6. 复制因子策略
  7. 可调一致性
  8. 查询驱动建模
  9. 反规范化实践
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

宽列模型基础

走进这家名为“Cassandra“的未来图书馆,你会发现书架的设计完全超出想象。传统图书馆的书架每一层必须容纳规格统一的书——行高、列宽、栏目标签都预先固定。而这里的”宽列书架“可以自由伸缩:同一排书架上,有的书只有 3 页,有的书有 3000 页,甚至每本书的标签页结构都完全不同。这正是宽列模型最核心的思想——以”行”为最基本单位,每一行可以持有任意多、任意结构的列。

这种设计在计算机术语中叫“宽列存储”。与传统关系型数据库先定义表结构(Schema)再填充数据不同,Cassandra 允许同一张表中的不同行拥有完全不同的列集合:某一行可能有“手机号”列,另一行没有,而第三行则多了“公司”列。动态列就像变形金刚一样让表结构随数据自适配,而稀疏存储则意味着空列完全不占用任何磁盘空间。

这种设计的直接收益是海量写入能力。传统数据库每次写入都要校验每一列是否符合预定义的类型和约束,宽列模型则把“写一行”简化为“追加一条记录”,配合稀疏存储特性,让单节点每秒可以完成数万次写入。驱动这种写入能力的表结构,可以用主键的构成来理解:

回到图书馆的比喻:宽列书架让每本书都能拥有独特空间,不必迁就统一的格子。这就是 Cassandra 能从容应对亿级海量写入的底层秘密——把复杂性从“数据适配表结构”转移到了“表结构适配数据”。

Primary Key=Partition Key+Clustering Columns\text{Primary Key} = \text{Partition Key} + \text{Clustering Columns}
  1. 主键组成Cassandra 表的主键由分区键和聚簇列两部分拼接而成,定义了一行数据“存哪里”和“怎么排”。
    Primary Key=Partition Key+Clustering Columns\text{Primary Key} = \text{Partition Key} + \text{Clustering Columns}
  2. 分区键职责它是主键的第一部分,决定这一行的数据存放在集群中的哪个分区,是数据的“地址簿”。
    Partition Key=PRIMARY KEY 的第一部分\text{Partition Key} = \text{PRIMARY KEY 的第一部分}
  3. 聚簇列职责它是主键的剩余部分,决定同一分区内多行数据的排列先后,是分区内的“秩序规则”。
    Clustering Columns=PRIMARY KEY 的其余列\text{Clustering Columns} = \text{PRIMARY KEY 的其余列}

分区键设计

在这个未来图书馆里,每本新书入库前都要打一个“分类编号”。这个编号决定它进入哪个分馆的书库。Cassandra 的分区键担当同样的角色:它通过哈希计算,决定每一行数据存放在集群中的哪个节点、哪个分区。

分区键是主键的第一部分,可以是一个列,也可以是多个列的组合(复合分区键)。它的设计质量直接决定数据分布的均匀性。如果分区键选得巧,数据像撒芝麻一样均匀落在集群所有节点上;如果选得偏,某些节点会“满载超载”,形成访问热点——这是 Cassandra 运维中最常见的性能杀手。

为什么数据分布如此重要?因为 Cassandra 是分布式系统,数据均匀分布是存储与读取效率的基石。设计分区键通常遵循“高基数列”原则——取值空间足够大、且每个值的记录条数大致相当。例如用“用户 ID”而不是“所在城市”作为分区键,因为一个城市可能有数百万用户,而一个用户只有几十条记录。

回到图书馆:分类编号设计得合理,每个书库的新书流量均衡;设计不好,某个书库天天爆满而其他书库闲置。Cassandra 建表的第一步永远是——为数据找到一把好的分区键。

token=hash(partition key)\text{token} = \text{hash}(\text{partition key})
  1. 哈希运算对分区键的值做加密哈希(如 MD5),把任意长度的字符串映射为一个固定长度的整数。
    hash(partition key)=128-bit 整数\text{hash}(\text{partition key}) = \text{128-bit 整数}
  2. 生成 token这个哈希值就是分区的 token,token 决定了数据在一致性哈希环上的落点位置。
    token=hash(partition key)\text{token} = \text{hash}(\text{partition key})
  3. 节点归属token 落在环上哪个弧段,数据就存储到该弧段对应的物理节点。
    节点归属=从 token 位置顺时针遇到的第一个节点\text{节点归属} = \text{从 token 位置顺时针遇到的第一个节点}

聚簇列排序

走进同一间书库,你会发现书并不是杂乱堆放的。管理员会按照某个规则排列——按出版年份从新到旧,或按作者姓氏拼音顺序。在 Cassandra 中,聚簇列就扮演这个“书库内部排序员”。

聚簇列是主键中除分区键以外的部分。它不决定数据分布在哪里,但决定同一个分区内所有行的物理排列顺序。Cassandra 将同一分区的数据按聚簇列值有序存储、连续写入磁盘,这让“按聚簇列范围取数”变得极其高效——读取时只需定位起点,然后顺序扫描即可,不需要跳来跳去。

最典型的应用是时间序列数据。比如物联网设备的温度记录,以“设备 ID”为分区键、以“采集时间戳”为聚簇列(逆序)。查询“某设备最近 1 小时的所有温度”只需在磁盘上顺序读一小段连续区域。聚簇列还支持多列组合排序,例如先按时间、再按优先级排列。

回到图书馆:书架上的书排列有序,找“2018 年出版的推理小说”只需顺着书架走一小段路。聚簇列就是让数据在“书架”上保持秩序的关键工具。

PRIMARY KEY(partition key, clustering col1, clustering col2)\text{PRIMARY KEY} \left( \text{partition key}, \ \text{clustering col}_1, \ \text{clustering col}_2 \right)
  1. 分区键开头主键的第一部分是分区键,它决定行数据属于哪个分区。
    PRIMARY KEY(partition key, … )\text{PRIMARY KEY} \left( \text{partition key}, \ \dots \right)
  2. 聚簇列随后分区键之后的列都是聚簇列,它们的组合值决定分区内的排序优先级。
    PRIMARY KEY(…, clustering col1, clustering col2)\text{PRIMARY KEY} \left( \dots, \ \text{clustering col}_1, \ \text{clustering col}_2 \right)
  3. 排序方向每个聚簇列可单独指定升序(ASC)或降序(DESC),时间序列场景常用 DESC 让最新数据排最前。
    clustering col ASC ∣ DESC\text{clustering col} \ \text{ASC} \ | \ \text{DESC}

一致性哈希

这座未来图书馆有一个惊艳设计:所有书库不是一字排开,而是围成一个巨大的圆环。每本新书按其分类编号哈希后“落”在圆环的某个位置,然后沿顺时针方向走,遇到第一个书库就入库。这就是一致性哈希环。

Cassandra 用一致性哈希决定数据映射到哪个节点。数据的分区键经过哈希得到一个 token,所有 token 构成一个从 0 到 $2^{64}-1$ 的环形空间。每个物理节点也通过哈希映射到环上的一个或多个位置。数据到来时,从它的 token 位置顺时针遇到的第一个节点就是归属节点。

一致性哈希最大的优势是节点增减的友好性。在普通取模哈希中,节点数从 4 变到 5,几乎所有数据都要重新分布;而一致性哈希只影响环上局部范围的数据,只有约 $\frac{1}{\text{节点数}}$ 的数据需要迁移。更进一步,Cassandra 引入虚拟节点(VNode)——每个物理节点在环上占据多个“虚拟位置”,让负载分布更均匀、节点上线退出时迁移粒度更小。

环形书库让管理员增减书库时只需搬动少量图书,这比传统图书馆“重排所有书架”的代价小得多,也正是 Cassandra 弹性扩展能力的底层基石。

token=hash(partition key) mod 264\text{token} = \text{hash}(\text{partition key}) \bmod 2^{64}
  1. 哈希函数对分区键做哈希运算,得到一个均匀分布的数值。
    hash(partition key)\text{hash}(\text{partition key})
  2. 取模定位对 $2^{64}$ 取模,将数值映射到 $[0, 2^{64})$ 的环状地址空间中。
    token=hash(partition key) mod 264\text{token} = \text{hash}(\text{partition key}) \bmod 2^{64}
  3. 顺时针寻径从 token 位置沿环顺时针移动,遇到的第一个节点即数据的归属者。
    owner=next node clockwise(token)\text{owner} = \text{next node clockwise} \left( \text{token} \right)

复制因子策略

重要图书不能只有一本——火灾、水灾或有人赖账不还,唯一的副本就永远消失了。任何像样的图书馆都会为贵重藏书准备副本,分放在不同书库。Cassandra 的复制因子(Replication Factor)就是设定“每份数据写几本副本”的配置。

复制因子以“每个分区”为单位:设为 3,意味着每个分区的数据包含 3 份副本,分别存放在不同的物理节点上。这 3 个副本不随机放置,而是遵循机架感知规则——优先将副本分布到不同机架甚至不同可用区,防止“一个机柜断电全灭”的悲剧。

复制因子的选择是一场权衡。设为 1,写入最快但任何节点故障都会导致数据不可用;设为 5,容错性强但写放大效应明显、存储成本成倍增加。生产环境通常选 3,在容错与成本间取得良好平衡。

图书馆管理员深谙此道:副本太多,书库空间和整理成本翻倍;副本太少,一场事故就能让珍贵藏书蒸发。复制因子 3 是经过生产实践检验的“金标准”。

可容忍故障节点数≤⌊N2⌋(QUORUM 读写策略下)\text{可容忍故障节点数} \le \left\lfloor \frac{N}{2} \right\rfloor \quad (\text{QUORUM 读写策略下})
  1. 复制因子 N每个分区保存 N 个副本,典型取值为 1、3、5。
    replicas=N\text{replicas} = N
  2. QUORUM 容错在 QUORUM 读写策略下,最多可容忍 $\lfloor N/2 \rfloor$ 个副本所在节点故障。
    fault tolerance=⌊32⌋=1(N=3)\text{fault tolerance} = \left\lfloor \frac{3}{2} \right\rfloor = 1 \quad (N=3)
  3. 成本权衡N 每增加 1,写入放大和存储成本约增加 $1/N$ 比例。
    overhead∝N\text{overhead} \propto N

可调一致性

你在这家图书馆查一本书——如果只是随便翻翻小说,那么任意找一本副本即可;但如果查的是法律条文或银行存款余额,你会坚持图书馆从多个书库的副本中交叉核对,确保读到的内容准确无误。这就是 Cassandra 的“可调一致性”。

Cassandra 允许每个读写请求指定一致性级别(Consistency Level):

  • ONE:只需 1 个副本响应,可用性最高、延迟最低,但可能读到旧数据。
  • QUORUM:需要超过一半的副本响应,在正确性与可用性间取得平衡。
  • ALL:所有副本必须响应,一致性最强但可用性最差,一个节点故障即不可用。

核心公式是:读一致性级别 R + 写一致性级别 W > 复制因子 N 时,读写路径必定在某个副本上相交,从而保证读取到最新写入的数据。

像图书馆管理员一样,你在不同场景调节信息核对标准:低价值数据用 ONE,重要金融数据用 QUORUM,极端关键状态用 ALL——以可用性换取正确性,始终是一场工程权衡。

R+W>NR + W > N
  1. 读一致性 R读请求需要从 R 个副本读取并对比结果。
    R=读副本数\text{R} = \text{读副本数}
  2. 写一致性 W写请求需要写入 W 个副本才算成功。
    W=写副本数\text{W} = \text{写副本数}
  3. 强一致条件当 $R + W > N$,读集合与写集合必有交集,交集中的副本一定持有最新写入的数据。
    R+W>N⇒读集合∩写集合≠∅R + W > N \Rightarrow \text{读集合} \cap \text{写集合} \neq \varnothing

查询驱动建模

传统图书馆先盖好固定格局的书库,再让读者适应书目分类。而 Cassandra 图书馆的建造哲学是反过来的:先调查读者都在问什么问题,再按照问题来设计书架摆放。这个思想被称为“查询驱动建模”(Query-Driven Modeling)。

在关系型数据库中,我们“先建表、后写查询”,表结构按实体关系规范化设计,查询时用 JOIN 把多张表拼接。Cassandra 则完全告别 JOIN——它认为 JOIN 在分布式环境下是性能毒药。正确做法是:先列出应用需要的所有查询,再为每个查询设计一张表,让表的主键直接匹配查询条件。

举个例子,社交应用需要“按用户 ID 查最近帖子”和“按帖子 ID 查点赞用户”,那就分别设计两张表,每张表的主键恰好对应一个查询模式。数据写两次,但读取时一次定位、零连接,延迟极低。

每个读者的问题都对应一本“专属索引手册”——查询驱动建模让每次查询都有最优路径,但也要求你彻底放下“先有表后有查询”的关系型思维惯性。

查询模式⇒PRIMARY KEY(partition key, clustering columns)\text{查询模式} \Rightarrow \text{PRIMARY KEY} \left( \text{partition key}, \ \text{clustering columns} \right)
  1. 识别查询模式梳理应用的每个读写路径,提取等值查询条件和排序/范围条件。
    Query=等值条件+排序条件\text{Query} = \text{等值条件} + \text{排序条件}
  2. 映射分区键WHERE 子句中的等值条件列作为分区键。
    Partition Key←WHERE 等值列\text{Partition Key} \leftarrow \text{WHERE 等值列}
  3. 映射聚簇列ORDER BY 或范围查询的列作为聚簇列,承担排序职责。
    Clustering Column←排序/范围条件列\text{Clustering Column} \leftarrow \text{排序/范围条件列}

反规范化实践

在这个未来图书馆里,你会发现同一本书的同一章节出现在不同书库——不是管理员糊涂,而是有意为之。为了让读者在任意分馆快速找到关键信息,图书馆会在多处放置内容相同的“速查卡”。这就是反规范化:用空间冗余换取查询效率。

Cassandra 从根上就是为反规范化设计的。前面提到“每个查询建一张表”——这意味着同一份业务数据经常以不同主键顺序存储多份。除了应用自己维护冗余外,Cassandra 还提供物化视图(Materialized View)自动完成这件事:你创建一张基表,再定义物化视图指定“以另一组列作为主键”,Cassandra 会像投影仪一样自动把基表的每次变更同步到视图中。

工程实践中反规范化有几个要点:优先使用物化视图减少应用侧维护成本;注意物化视图的额外写入开销(每次基表写入都要更新视图);对于高 QPS 场景,干脆在应用层显式写多张表,获得最大控制力和可见性。

多放几份“速查卡”看似浪费空间,却让读者在每一个分馆都能瞬间找到答案——Cassandra 建模的全部智慧,本质上都浓缩在“反规范化换取查询效率”这句权衡口诀之中。

物化视图=基表数据+新的主键排序\text{物化视图} = \text{基表数据} + \text{新的主键排序}
  1. 基表主表保存原始数据,以主键(分区键+聚簇列)提供一种查询视角。
    base table=原始行数据\text{base table} = \text{原始行数据}
  2. 物化视图以不同主键(新的分区键与聚簇列组合)保存同一批数据的另一份副本。
    materialized view=same rows+new PRIMARY KEY\text{materialized view} = \text{same rows} + \text{new PRIMARY KEY}
  3. 自动同步Cassandra 在写基表时自动更新对应物化视图,应用无需手动维护双写逻辑。
    write base→自动update view\text{write base} \xrightarrow{\text{自动}} \text{update view}

AI 应用场景

物联网时序数据采集平台

阶段:数据存储与预处理

解决的问题:千万级智能设备每秒上报温度、电量等指标,传统关系型数据库单点写入瓶颈导致大量数据丢失。Cassandra 的宽列模型配合分区键 + 时间戳聚簇列,让每台设备的数据连续写入同一分区并按时间排序,支撑每秒数十万次写入。

具体案例:某智能电表平台部署 6 节点 Cassandra 集群,每 15 分钟接收 2000 万只电表的读数。以“表箱 ID”为分区键、“采集时间”为聚簇列(DESC),查询某只电表最近 24 小时曲线只需扫描一个分区的连续数据块,P99 延迟稳定在 20ms 以内。

实时推荐系统的用户行为特征服务

阶段:特征工程与在线服务

解决的问题:推荐系统需要实时读取用户最近点击、收藏、购买行为用于特征拼接。传统方案用 Redis 存储键值列表但缺少持久化,用 MySQL 则查询延迟高。Cassandra 查询驱动建模为“按 user_id 查最近 N 条行为”单独建表,读写都走主键,毫秒级返回。

具体案例:某电商推荐平台将用户点击流写入 Cassandra,行以“用户 ID”为分区键、“行为时间”为聚簇列,同时维护一张“物品被谁点击过”的反向表(反规范化)。模型在线推理时并行查询两张表,拼接出用户最近意图与物品全局热度,端到端特征耗时从 200ms 降到 35ms。

社交平台消息推送的持久层

阶段:消息队列与数据落库

解决的问题:IM 和高并发推送场景要求“按用户 ID 实时拉取会话列表”,同时要保证写入不丢。Cassandra 天然支持多活写入,多个数据中心各持一份副本,配合 QUORUM 一致性级别在跨地域容灾和读取正确性之间取得平衡。

具体案例:某社交 App 的消息持久层采用双数据中心 Cassandra 部署,复制因子 3,业务写入用 LOCAL_QUORUM 确认本中心两副本即返回;同城机房故障时自动切换读流量到另一中心,消息不丢失、用户体验无损。数据量从 20 亿条增长到 200 亿条期间,集群仅靠横向加节点平滑扩展。

Python 演示

python73 行
# Simple consistent hash ring simulator for Cassandra data modelimport hashlib  class ConsistentHashRing:    """Simulate Cassandra's consistent hash ring with vnodes and replication."""     def __init__(self, nodes=None, vnode_count=3, replication_factor=3):        self.vnode_count = vnode_count        self.replication_factor = replication_factor        self.ring = {}        if nodes:            for node in nodes:                self.add_node(node)     def _hash(self, key):        return int(hashlib.md5(key.encode()).hexdigest(), 16)     def add_node(self, node):        for i in range(self.vnode_count):            vnode_key = f"{node}#vn{i}"            self.ring[self._hash(vnode_key)] = node     def remove_node(self, node):        for i in range(self.vnode_count):            vnode_key = f"{node}#vn{i}"            self.ring.pop(self._hash(vnode_key), None)     def _successors(self, token):        sorted_tokens = sorted(self.ring.keys())        for t in sorted_tokens:            if t >= token:                yield t        for t in sorted_tokens:            yield t     def locate(self, key):        token = self._hash(key)        result = []        for ring_token in self._successors(token):            node = self.ring[ring_token]            if node not in result:                result.append(node)            if len(result) >= self.replication_factor:                break        return result     def distribution(self, keys):        stats = {}        for key in keys:            primary = self.locate(key)[0]            stats[primary] = stats.get(primary, 0) + 1        return stats  if __name__ == "__main__":    ring = ConsistentHashRing(        nodes=["node-a", "node-b", "node-c", "node-d"],        vnode_count=3,        replication_factor=3    )    sample_keys = [f"user:{i}" for i in range(1000)]    dist = ring.distribution(sample_keys)    print("Distribution of 1000 keys across nodes:")    for node, count in sorted(dist.items()):        bar = "#" * (count // 20)        print(f"  {node}: {count}  {bar}")    print("Replicas of 'user:42':", ring.locate("user:42"))    ring.remove_node("node-c")    new_dist = ring.distribution(sample_keys)    print("After removing node-c:")    for node, count in sorted(new_dist.items()):        print(f"  {node}: {count} keys")
目的:用 Python 实现一个简化版一致性哈希环,模拟 Cassandra 如何通过哈希环分布数据、复制副本,以及节点增减时数据如何重新分布。
  1. self.ring[self._hash(vnode_key)] = node将每个虚拟节点哈希后写入环映射表,一个物理节点对应多个环上位置。
  2. def _successors(self, token)生成器从 token 位置出发按顺时针遍历环上所有 token,模拟“顺时针找最近节点”。
  3. if len(result) >= self.replication_factor: break收集到复制因子数量的不同物理节点后停止,这就是副本分配逻辑。
  4. stats[primary] = stats.get(primary, 0) + 1统计每个节点作为主副本承载的键数量,用于观察负载均衡效果。
  5. ring.remove_node("node-c")移除一个节点后重新统计分布,直观展示一致性哈希只引起少量数据迁移。

常见误区

误区听到“宽列模型”就以为是类似 ClickHouse 的列式 OLAP 数据库,拿来跑大范围聚合分析、GROUP BY 查询。

正解Cassandra 的“宽列”指的是每行可以有任意多的列,但物理存储是按行组织的——一行的所有列在磁盘上连续存放。它本质上是“按行键分区存储”的分布式键值数据库,擅长高并发点查和范围扫描,不擅长全表聚合分析。宽列模型是“行的横向扩展”,而非“列的纵向压缩”。

为何常见:命名误导。“宽列”和“列式存储”看起来相似,但两者存储组织方式完全不同。很多初学 NoSQL 的人把列族存储和列式存储混为一谈。

误区设计表时拼命往分区键里塞列,以为“组合键携带更多信息,分布一定更均匀”。

正解数据均匀性取决于分区键值的实际基数和真实分布。如果复合分区键的第一个列是“城市”,第二个列是“用户 ID”,集群仍可能因为某个城市的用户量巨大而形成热点。分布均匀的关键是让每个分区键值对应的数据量大致相同,而不是分区键包含的列数多。

为何常见:把“信息量”和“分布均匀性”混为一谈,忽略了哈希分布的真实决定因素是值域命中概率。

误区把复制因子调到 5 甚至更高,认为副本越多越保险。

正解复制因子增加时,每次写入都要同步到更多副本,写入延迟和失败概率随之上升,存储成本线性增长,而容错收益呈边际递减。N=3 已能容忍单节点故障(QUORUM 下),N=5 虽可容忍两节点故障,但运维复杂度和成本大幅增加。生产环境应先选 N=3,再结合机架感知策略而不是盲目堆副本数量。

为何常见:只盯着容错收益,没有理解写入放大、存储成本和跨副本同步延迟等隐形成本。

误区认为 ALL 就是“所有副本都确认”,等于实现了强一致,可以放心读。

正解ALL 只是让读写路径覆盖所有副本,并不能提供跨数据中心的全局线性一致性。而且 ALL 的可用性极差——只要有一个副本所在节点宕机,写请求就直接失败。即便用了 ALL,Cassandra 的分布式事务和时间戳机制仍无法保证绝对的全局串行化。真正需要强一致的业务应使用 R + W > N 的 QUORUM 组合,并接受跨机房延迟。

为何常见:把“读取到最新数据”和“所有副本同步写入”混为一谈,没有理解分布式系统下网络分区和时钟漂移的复杂性。

误区把 CQL 当成 MySQL 的方言,直接写 `WHERE some_column = 'x'` 查询非主键列,认为数据库会自动用索引加速。

正解Cassandra 的查询必须走主键路径——WHERE 中的等值列必须包含完整分区键,排序条件必须匹配聚簇列。对非主键列查询会触发全节点扫描(甚至全集群扫描),性能灾难级。正确做法是查询驱动建模:为这个查询单独设计一张表,把目标列提升为分区键。

为何常见:CQL 的语法长得太像 SQL 了,让人误以为执行引擎也相同。实际两者底层存储结构和查询计划完全不同。

面试题

Q1. Cassandra 的数据模型属于以下哪一类?

单选题 · easy

  • A.键值存储
  • B.文档存储
  • C.宽列存储← 正确答案
  • D.图数据库

答案:C。Cassandra 是典型的宽列存储(宽列表)数据模型,以行键 + 动态列集合组织数据。它既不是纯粹的键值存储,也不是文档存储或图数据库。“宽列”指的是每行可有任意数量的列,列可按需动态添加。

Q2. 在 Cassandra 中,决定一行数据在集群中物理存放位置的键是?

单选题 · easy

  • A.聚簇列
  • B.分区键← 正确答案
  • C.次级索引
  • D.外键

答案:B。分区键经过哈希计算生成 token,token 决定数据在一致性哈希环上的位置,进而确定数据由哪个节点存储。聚簇列只负责分区内排序,不参与数据分布决策。

Q3. 关于聚簇列,以下说法正确的是?

单选题 · mid

  • A.聚簇列决定数据分布节点
  • B.聚簇列定义分区内排序← 正确答案
  • C.聚簇列不参与表主键
  • D.聚簇列用于负载均衡

答案:B。聚簇列是主键中除分区键以外的列,它决定同一分区内所有行的物理排列顺序。分区键负责数据分布(负载均衡),聚簇列负责分区内秩序,两者职责完全不同。

Q4. 关于一致性哈希分区,以下哪些说法是正确的?

多选题 · mid

  • A.节点增减仅影响少量数据迁移← 正确答案
  • B.虚拟节点有助于负载均衡← 正确答案
  • C.一致性哈希可以避免数据复制
  • D.数据映射到环上的某个位置← 正确答案

答案:ABD。一致性哈希的核心优势就是节点增减时只影响环上局部数据(A 正确)。虚拟节点让每个物理节点在环上占据多个位置,从而让负载更均匀(B 正确)。一致性哈希是数据分布策略,与复制机制无关(C 错误)。数据键哈希成 token 后映射到环上的一个位置(D 正确)。

Q5. 复制因子 N=3、采用 QUORUM 读写时,最多可容忍几个节点同时故障而不丢失写入?

单选题 · mid

  • A.1 个← 正确答案
  • B.2 个
  • C.3 个
  • D.4 个

答案:A。QUORUM 要求读写都覆盖超过一半副本,即 2 个。N=3 时若 2 个节点故障,存活的 1 个副本无法凑齐 QUORUM,读写都会失败。因此最多容忍 1 个节点故障,公式为 ⌊N/2⌋ = 1。

Q6. 在 Cassandra 可调一致性中,保证读写强一致的条件是(N 为复制因子,R 为读一致性级别,W 为写一致性级别)?

单选题 · mid

  • A.R + W > N← 正确答案
  • B.R + W = N
  • C.R + W < N
  • D.R + W ≥ N

答案:A。当读取覆盖的副本集合与写入覆盖的副本集合有交集时,读请求必然能拿到最新写入的数据。R + W > N 保证两个集合必有交集。等于 N 时可能恰好无交集(取决于具体副本分布),因此不满足强一致条件。

Q7. 以下哪种查询在 Cassandra 中执行效率最高?

单选题 · hard

  • A.按用户 ID 等值查最近 10 条记录← 正确答案
  • B.多表 JOIN 聚合统计
  • C.全表扫描求平均值
  • D.任意列组合过滤查询

答案:A。按分区键等值查询(用户 ID 作为分区键)加聚簇列排序(时间戳)只需访问单个分区的一段连续数据,是 Cassandra 最优路径。JOIN、全表扫描、非主键过滤都会导致跨节点全量扫描,违背查询驱动建模原则。

Q8. Cassandra 反规范化建模的常见做法包括哪些?

多选题 · hard

  • A.按查询模式设计多张冗余表← 正确答案
  • B.将热门统计字段冗余存储← 正确答案
  • C.使用物化视图自动维护排序← 正确答案
  • D.用外键关联多张规范表

答案:ABC。查询驱动建模天然要求为每个查询建一张冗余表(A 正确);把点赞数、订单量等高频字段冗余到主表中避免额外查询(B 正确);物化视图自动以另一种主键顺序同步数据(C 正确)。外键与 JOIN 是关系型数据库的建模方式,Cassandra 不支持也不推荐。

Q9. 关于虚拟节点(vnode),以下描述正确的是?

单选题 · easy

  • A.每个物理节点在环上对应多个虚拟位置← 正确答案
  • B.虚拟节点用于存储冗余备份
  • C.虚拟节点会破坏数据分布均匀性
  • D.虚拟节点只用于小规模集群

答案:A。虚拟节点让每个物理节点在哈希环上占据多个位置,使数据分布更均匀、节点增减时的迁移粒度更小。它不存储冗余数据(复制是副本因子的事),也不会破坏均匀性,而是增强均匀性。

Q10. 一个 RF=3 的 Cassandra 集群,向 3 个副本写入时使用 W=2 且已确认成功。随后持有最新数据的副本所在节点宕机,此时执行 R=2 的 QUORUM 读请求,结果会怎样?

单选题 · extreme

  • A.一定会读到最新数据
  • B.一定读不到最新数据
  • C.取决于存活副本是否持有最新数据← 正确答案
  • D.读请求会直接失败

答案:C。W=2 意味着写入只确认了 2 个副本,另一个副本可能未写入最新数据。若宕机的恰好是最新数据的持有者,R=2 的读请求可能遇到两个存活副本都是旧数据(若写入确认的 2 个副本中恰好包含宕机节点),此时会读到旧值;若写入确认的 2 个副本中有 2 个存活,则能读到新值。因此结果不确定,选 C。