在大规模分布式系统中,关系型数据库往往难以满足高写入吞吐和跨地域高可用的需求。Apache Cassandra 作为一款开源的分布式 NoSQL 数据库,凭借其无中心架构、线性可扩展性和多数据中心复制能力,成为了 Netflix、Apple、Instagram 等超大规模业务的核心存储基础设施。本文将从数据模型设计、集群部署、读写路径原理到性能调优,全面剖析 Cassandra 的核心技术。

一、Cassandra 核心架构与设计哲学
Cassandra 的架构设计融合了 Amazon Dynamo 的分布式系统思想和 Google Bigtable 的数据模型。理解其核心设计哲学,是正确使用 Cassandra 的前提。
1.1 无中心(Peer-to-Peer)架构
与 MySQL 主从复制或 MongoDB 副本集不同,Cassandra 没有主节点。集群中的每个节点完全对等,客户端可以连接任意节点进行读写操作。被连接的节点作为协调者(Coordinator),负责将请求路由到正确的副本节点。这种设计带来了几个关键优势:
- 无单点故障:任何节点宕机不影响集群整体可用性
- 读写均可线性扩展:增加节点即可提升吞吐量
- 多活写入:不同数据中心可以同时接受写入请求
1.2 一致性哈希与 Token Ring
Cassandra 使用一致性哈希将数据分布到集群节点。每个节点被分配一个 Token 范围,数据根据分区键(Partition Key)的哈希值映射到对应的 Token 区间。当集群扩容或缩容时,只需要迁移受影响 Token 范围的数据,最小化数据移动开销。
1
2
3
4
5
6
7
8
9 # 查看集群 Token 分布
nodetool ring
# 输出示例
# Datacenter: dc1
# ========== Address Rack Status State Load Owns Token
# 10.0.1.1 rack1 Up Normal 150.32 GB 33.33% -9223372036854775808
# 10.0.1.2 rack1 Up Normal 149.87 GB 33.33% -3074457345618258603
# 10.0.1.3 rack1 Up Normal 150.11 GB 33.33% 3074457345618258602
1.3 复制策略与一致性级别
Cassandra 支持两种复制策略:
1 | SimpleStrategy |
(单数据中心)和
1 | NetworkTopologyStrategy |
(多数据中心)。生产环境务必使用后者,它可以精确控制每个数据中心的副本数量。
1
2
3
4
5
6
7
8 -- 创建多数据中心 Keyspace
CREATE KEYSPACE production
WITH replication = {
'class': 'NetworkTopologyStrategy',
'dc1': 3, -- 数据中心1 3个副本
'dc2': 2 -- 数据中心2 2个副本
}
AND durable_writes = true;
一致性级别(Consistency Level)决定了读写操作需要多少个副本确认才算成功。这是一致性与可用性之间的核心权衡:
| 一致性级别 | 含义 | 典型场景 |
|---|---|---|
| ONE | 只需1个副本确认 | 高吞吐、低延迟场景 |
| QUORUM | 多数副本确认((RF/2)+1) | 强一致性读写 |
| ALL | 所有副本确认 | 最高一致性、最低可用性 |
| LOCAL_QUORUM | 本地DC多数副本确认 | 跨DC部署推荐 |
| EACH_QUORUM | 每个DC都达到QUORUM | 严格跨DC一致性 |
经典公式:读 QUORUM + 写 QUORUM > RF,可以保证读到最新写入的数据。例如 RF=3 时,读写均使用 QUORUM(2+2>3),即可实现强一致读。

二、数据模型设计:查询驱动思维
Cassandra 的数据模型设计与关系型数据库有本质区别。RDBMS 是数据驱动建模(先设计范式化表,再写查询),而 Cassandra 是查询驱动建模(先确定查询,再设计表结构)。一条黄金法则是:一张表对应一个查询。
2.1 主键设计的深层原理
Cassandra 的主键由分区键(Partition Key)和聚簇列(Clustering Columns)组成,它们决定了数据的物理分布和排序方式:
1
2
3
4
5
6
7
8
9
10 -- 复合分区键 + 聚簇列
CREATE TABLE user_activity (
user_id uuid,
activity_date date,
activity_time timestamp,
activity_type text,
description text,
PRIMARY KEY ((user_id, activity_date), activity_time)
) WITH CLUSTERING ORDER BY (activity_time DESC)
AND compaction = {'class': 'TimeWindowCompactionStrategy'};
在这个例子中:
-
1(user_id, activity_date)
是复合分区键,决定数据分布到哪个节点
-
1activity_time
是聚簇列,决定分区内数据的排序方向
- 同一分区内的数据存储在同一个 SSTable,读取效率极高
2.2 反范式化是最佳实践
在关系型数据库中,反范式化通常被视为优化手段;但在 Cassandra 中,反范式化是基本的建模方式。为了满足不同的查询模式,需要创建多张表存储相同数据的不同视图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 -- 查询1:按用户查订单
CREATE TABLE orders_by_user (
user_id uuid,
order_date date,
order_id uuid,
total decimal,
status text,
PRIMARY KEY ((user_id), order_date, order_id)
) WITH CLUSTERING ORDER BY (order_date DESC, order_id ASC);
-- 查询2:按商品查订单
CREATE TABLE orders_by_product (
product_id uuid,
order_date date,
order_id uuid,
user_id uuid,
quantity int,
PRIMARY KEY ((product_id), order_date, order_id)
) WITH CLUSTERING ORDER BY (order_date DESC, order_id ASC);
-- 查询3:按状态查待处理订单
CREATE TABLE orders_by_status (
status text,
order_date date,
order_id uuid,
user_id uuid,
total decimal,
PRIMARY KEY ((status), order_date, order_id)
) WITH CLUSTERING ORDER BY (order_date DESC, order_id ASC);
虽然数据冗余存储了三次,但每张表的查询都是高效的分区查询,避免了跨节点扫描。这种设计换取了读取性能,代价是写入时需要同步更新多张表。可以通过批量操作(Batch)保证多表更新的原子性:
1
2
3
4
5
6
7
8
9
10 BEGIN BATCH
INSERT INTO orders_by_user (user_id, order_date, order_id, total, status)
VALUES (123e4567-e89b-12d3-a456-426614174000, '2025-01-15', now(), 299.99, 'PENDING');
INSERT INTO orders_by_product (product_id, order_date, order_id, user_id, quantity)
VALUES (789e0123-e45b-67d3-c890-123456789012, '2025-01-15', now(), 123e4567-e89b-12d3-a456-426614174000, 2);
INSERT INTO orders_by_status (status, order_date, order_id, user_id, total)
VALUES ('PENDING', '2025-01-15', now(), 123e4567-e89b-12d3-a456-426614174000, 299.99);
APPLY BATCH;
2.3 分区键设计常见陷阱
分区键设计是 Cassandra 性能的命脉。以下是生产环境最常见的三个错误:
陷阱一:分区过大(Hot Partition)。如果分区键的基数太低(例如按城市分区),会导致某些分区远大于其他分区。建议单个分区不超过 100MB,包含的行数不超过 100,000 行。
陷阱二:分区过小(Bucket 太细)。如果分区键粒度太细(例如按毫秒时间戳分区),虽然分区均匀,但查询需要跨大量分区扫描,效率极低。
陷阱三:使用 ALLOW FILTERING。这个选项允许对非分区键字段进行过滤,但会导致全表扫描,生产环境应当禁止:
1
2
3
4
5
6
7
8
9
10 -- 禁止:会导致全表扫描
SELECT * FROM users WHERE email = 'test@example.com' ALLOW FILTERING;
-- 正确:创建以 email 为分区键的查询表
CREATE TABLE users_by_email (
email text,
user_id uuid,
name text,
PRIMARY KEY ((email))
);

三、写入与读取路径深度解析
理解 Cassandra 的读写路径,是进行性能调优的基础。Cassandra 采用 LSM-Tree(Log-Structured Merge-Tree)存储引擎,写入路径针对高吞吐做了极致优化。
3.1 写入路径
一次写入操作经过以下步骤:
- 写 Commit Log:数据首先追加写入磁盘上的 Commit Log,保证持久性(宕机可恢复)
- 写 Memtable:数据写入内存中的 Memtable,按分区键组织并按聚簇列排序
- 返回确认:根据一致性级别,等待足够数量的副本确认后返回成功
- 刷盘(Flush):当 Memtable 达到阈值(默认 256MB),刷写到磁盘成为 SSTable
- 压缩(Compaction):后台合并多个 SSTable,清除过期和已删除数据
1
2
3
4
5
6
7
8 # 监控 Memtable 和 SSTable 状态
nodetool tablestats keyspace_name.table_name
# 手动触发刷盘
nodetool flush keyspace_name table_name
# 查看压缩任务
nodetool compactionstats
3.2 读取路径
读取路径相对复杂,需要合并多个数据源:
- 检查 Row Cache:如果启用,首先检查行缓存
- 检查 Key Cache + Bloom Filter:定位可能包含目标数据的 SSTable
- 检查 Memtable:最新的数据在内存中
- 扫描 SSTable:按时间从新到旧扫描相关 SSTable,合并结果
Bloom Filter 是 Cassandra 读取性能的关键优化。它是一个概率数据结构,可以快速判断某个 SSTable 不包含指定分区键的数据(可能有假阳性,但不会有假阴性),从而避免大量无效磁盘 IO。
3.3 压缩策略选择
压缩策略直接影响读放大、写放大和空间放大之间的平衡:
| 策略 | 适用场景 | 读放大 | 写放大 | 空间放大 |
|---|---|---|---|---|
| SizeTieredCompactionStrategy (STCS) | 写密集型 | 高 | 低 | 高 |
| LeveledCompactionStrategy (LCS) | 读密集型 | 低 | 高 | 低 |
| TimeWindowCompactionStrategy (TWCS) | 时序数据 | 低 | 低 | 低 |
对于时序数据场景,TWCS 是最佳选择。它按时间窗口分组 SSTable,同一时间窗口内的 SSTable 不会互相合并,过期数据可以通过 TTL 自动清理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 CREATE TABLE sensor_readings (
sensor_id uuid,
bucket_hour timestamp,
reading_time timestamp,
value double,
unit text,
PRIMARY KEY ((sensor_id, bucket_hour), reading_time)
) WITH CLUSTERING ORDER BY (reading_time ASC)
AND compaction = {
'class': 'TimeWindowCompactionStrategy',
'compaction_window_size': 1,
'compaction_window_unit': 'HOURS'
}
AND default_time_to_live = 604800; -- 7天自动过期
四、集群部署与运维实战
4.1 生产环境配置要点
1 | cassandra.yaml |
是核心配置文件,以下是生产环境必须调整的关键参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 # cassandra.yaml 生产配置
# 集群名称,同一集群必须相同
cluster_name: 'ProductionCluster'
# 种子节点,建议2-3个,不要把所有节点都设为种子
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "10.0.1.1,10.0.1.2"
# 监听地址
listen_address: 10.0.1.1
rpc_address: 10.0.1.1
# Snitch 决定拓扑感知,生产环境用 GossipingPropertyFileSnitch
endpoint_snitch: GossipingPropertyFileSnitch
# 并发读写线程数,通常设为 CPU 核心数
concurrent_reads: 32
concurrent_writes: 32
# Memtable 刷盘阈值
memtable_flush_writers: 4
# 堆内存建议不超过 16GB(超过会导致 GC 问题)
MAX_HEAP_SIZE: 8G
HEAP_NEWSIZE: 2G
4.2 多数据中心部署
Cassandra 原生支持多数据中心部署,无需额外的复制中间件。关键配置在
1 | cassandra-rackdc.properties |
:
1
2
3
4
5
6
7 # 数据中心1 节点配置
dc=dc1
rack=rack1
# 数据中心2 节点配置
dc=dc2
rack=rack1
多数据中心场景下,建议使用
1 | LOCAL_QUORUM |
作为默认一致性级别。它只在本地数据中心内等待 QUORUM 确认,避免跨数据中心延迟影响写入性能,同时通过 Hinted Handoff 和 Read Repair 保证跨 DC 的最终一致性。
4.3 常用运维操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 # 集群状态检查
nodetool status
# 修复特定 Keyspace(建议每周运行一次全量修复)
nodetool repair keyspace_name
# 增量修复(推荐,减少修复时间)
nodetool repair keyspace_name --incremental
# 节点退役(安全移除节点)
nodetool decommission
# 节点替换(替换宕机节点)
# 1. 在新节点 cassandra.yaml 中设置 replace_address
# 2. 启动新节点,自动从其他副本拉取数据
# 查看压缩统计
nodetool compactionstats
# 清理已删除数据的空间
nodetool garbagecollect keyspace_name table_name
# 查看表级统计
nodetool tablestats
4.4 备份与恢复策略
Cassandra 提供快照(Snapshot)功能进行数据备份,快照是对 SSTable 文件的硬链接,不占用额外磁盘空间:
1
2
3
4
5
6
7
8
9
10
11
12
13 # 创建全量快照
nodetool snapshot keyspace_name -t backup_20250115
# 查看快照
nodetool listsnapshots
# 清理快照
nodetool clearsnapshot -t backup_20250115
# 增量备份(在快照之间开启)
# 在 cassandra.yaml 中设置:
# incremental_backups: true
# 增量备份文件会输出到 backups/ 子目录

五、性能调优与故障排查
5.1 JVM 调优
Cassandra 运行在 JVM 上,GC 调优对性能至关重要。生产环境的关键建议:
- 堆内存不超过 16GB,否则 G1 GC 的停顿时间不可控
- 使用 G1GC(Cassandra 4.x 默认),不要使用 CMS
- 设置合适的
1InitiatingHeapOccupancyPercent
(默认 45,可适当降低)
- 监控 GC 日志,关注 Full GC 频率和停顿时间
1
2
3
4
5
6 # jvm.options 中关键参数
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=35
-XX:+ParallelRefProcEnabled
-XX:G1RSetUpdatingPauseTimePercent=5
5.2 常见性能问题与解决方案
问题一:读取延迟 P99 飙高
- 检查是否分区过大:使用
1nodetool tablehistograms
查看分区大小分布
- 检查 Bloom Filter 命中率:
1nodetool tablestats
中的 bloom_filter_false_ratio 应低于 1%
- 检查 Key Cache 命中率:应高于 85%
- 检查 Compaction 积压:
1nodetool compactionstats
问题二:写入吞吐下降
- 检查 Commit Log 磁盘 IO:建议 Commit Log 放在独立 SSD
- 检查是否因 Repair 导致大量读操作:将 Repair 安排在低峰期
- 检查 Compaction 是否跟不上写入速度:调整
1compaction_throughput_mb_per_sec
问题三:节点间数据不一致
- 运行
1nodetool repair
同步差异数据
- 检查 Read Repair 概率:
1read_repair_chance
默认 0.1,可根据一致性要求调整
- 检查 Hinted Handoff 窗口:
1max_hint_window_in_ms
默认 3 小时
5.3 监控指标体系
通过 JMX 或 Prometheus + cassandra-exporter 暴露的关键指标:
| 指标分类 | 关键指标 | 告警阈值建议 |
|---|---|---|
| 延迟 | ReadLatency / WriteLatency (P99) | P99 > 100ms |
| 吞吐 | ReadCount / WriteCount | 趋势突降 |
| 缓存 | KeyCacheHitRate | < 80% |
| 压缩 | CompactionPendingTasks | > 50 |
| GC | GCPauseMillis (P99) | > 500ms |
| 集群 | DownEndpointCount | > 0 |
| 修复 | RepairTasksInProgress | 持续增长 |
六、Cassandra 与其他数据库的对比选型
在实际项目中,选型需要根据业务特征进行。以下对比有助于判断 Cassandra 是否适合你的场景:
| 维度 | Cassandra | MongoDB | PostgreSQL |
|---|---|---|---|
| 架构 | 无中心,P2P | 主从副本集 | 单主 + 只读副本 |
| 数据模型 | 宽列存储 | 文档存储 | 关系型 |
| 写入优化 | 极高(LSM-Tree) | 高 | 中等 |
| 复杂查询 | 弱(仅分区+范围) | 中(聚合管道) | 极强(SQL + JOIN) |
| 事务 | 单分区行级原子性 | 多文档事务(4.0+) | 完整 ACID |
| 跨DC复制 | 原生支持 | 需要额外配置 | 需要第三方工具 |
| 典型场景 | IoT 时序、消息、推荐 | 内容管理、用户画像 | 金融、ERP、复杂查询 |
Cassandra 的最佳适用场景包括:
- 高写入吞吐:IoT 传感器数据、日志收集、用户行为追踪
- 多地域部署:全球分布的在线服务需要就近读写
- 始终可写:系统不能因为任何单点故障拒绝写入
- 时间序列数据:结合 TWCS,天然适合时序场景
不推荐 Cassandra 的场景:需要复杂 JOIN 和聚合分析、需要强事务保证、数据量较小且查询模式多变。
总结
Apache Cassandra 的核心价值在于通过无中心架构和 LSM-Tree 存储引擎,实现了高可用、高写入吞吐和线性可扩展。正确的数据模型设计——查询驱动、一张表一个查询、适当反范式化——是发挥 Cassandra 性能优势的关键。在生产运维中,重点关注分区大小控制、压缩策略选择、JVM GC 调优和定期 Repair,可以确保集群长期稳定运行。对于需要始终可写、多地域部署、高写入吞吐的业务场景,Cassandra 依然是不可替代的选择。
汤不热吧