Cassandra 实战:宽表数据建模与高可用集群

Cassandra 是 Apache 旗下的分布式宽表(Wide-Column)NoSQL 数据库,天生为海量写入与跨机房高可用而生。它采用无主(Masterless)架构与一致性哈希,节点对等、可线性扩展,单机故障不影响整体可用。本文从数据建模、一致性级别讲到三节点集群部署,帮你把 Cassandra 真正落到生产。

一、Cassandra 解决什么问题

关系型数据库擅长复杂事务与join,但分库分表后扩展艰难;而Cassandra 把”写入吞吐”和”可用性”放到第一位,用最终一致换来了轻松的水平扩展。典型场景:物联网时序、设备上报、消息轨迹、画像标签、会话存储——写多读少、按主键查询、能接受秒级最终一致。

二、核心概念:Keyspace、表与宽表建模

Cassandra 的数据模型是”嵌套的 KV”:Keyspace 类似库,Table 类似表,但每一行由 partition key 决定落在哪个节点,clustering column 决定行内排序。建模的第一原则:先想清楚查询模式,再设计主键,而不是先画 ER 图。

2.1 创建 Keyspace

CREATE KEYSPACE IF NOT EXISTS iot
  WITH replication = {
    'class': 'SimpleStrategy',
    'replication_factor': 3
  };

生产跨机房要用 NetworkTopologyStrategy 并逐数据中心指定副本数,避免 SimpleStrategy 把三副本都放同一机架。

2.2 宽表建模:用聚类列支撑时间范围查询

CREATE TABLE iot.device_metrics (
  device_id   text,
  bucket      date,           -- 按天分桶,避免单分区无限膨胀
  ts          timestamp,
  temperature float,
  payload     text,
  PRIMARY KEY ((device_id, bucket), ts)
) WITH CLUSTERING ORDER BY (ts DESC);

上面的 (device_id, bucket) 是复合分区键,ts 是聚类列并倒序排列。这样”某设备某天的最新 N 条”就是一次高效的范围扫描,而不是全表 filter。

三、一致性级别与读写路径

Cassandra 是 AP 系统(CAP 里偏向可用性+分区容忍),一致性由一致性级别(Consistency Level)在每次请求上调节:ONE 最快但最终一致、QUORUM 平衡、ALL 最强但牺牲可用性。

-- 写时要求多数副本确认
CONSISTENCY QUORUM;
INSERT INTO iot.device_metrics (device_id, bucket, ts, temperature)
VALUES ('dev-01', '2026-09-25', toTimestamp(now()), 36.5);

-- 读时同样用 QUORUM,配合读修复
SELECT * FROM iot.device_metrics
WHERE device_id='dev-01' AND bucket='2026-09-25'
LIMIT 10;
一致性级别所需确认副本特点
ONE1 个最低延迟,可能读到旧值
QUORUM多数((RF/2)+1)延迟与一致性的平衡点
ALL全部强一致,任一副本宕机即写失败
LOCAL_QUORUM本数据中心多数跨机房场景首选,省跨城延迟

写路径:客户端请求 → 任一节点(coordinator)→ 写入 commitlog(持久化 WAL)和 memtable(内存)→ memtable 满后刷成不可变的 SSTable。读路径:先查 memtable,再合并相关 SSTable,并用读修复(read repair)把最新值回填落后副本。理解这条链路,才能解释”为什么偶尔读到旧数据”。

四、三节点集群部署(Docker Compose)

用 Docker 编排 起一个本地三节点集群,重点在 seeds、listen_address、rpc_address 三个配置。种子节点只用于发现,不是单点,建议至少配两个。

version: "3.8"
services:
  cass1:
    image: cassandra:5.0
    environment:
      - CASSANDRA_SEEDS=cass1,cass2
      - CASSANDRA_LISTEN_ADDRESS=cass1
      - CASSANDRA_RPC_ADDRESS=0.0.0.0
      - CASSANDRA_CLUSTER_NAME=fsdata-cluster
    ports: ["9042:9042"]
  cass2:
    image: cassandra:5.0
    environment:
      - CASSANDRA_SEEDS=cass1,cass2
      - CASSANDRA_LISTEN_ADDRESS=cass2
  cass3:
    image: cassandra:5.0
    environment:
      - CASSANDRA_SEEDS=cass1,cass2
      - CASSANDRA_LISTEN_ADDRESS=cass3

节点启动后,用 nodetool status 查看各节点 UN(Up/Normal)状态与负责的令牌区间。生产上数据盘建议独立挂载,并参考 K8s 持久化存储 的 PV/PVC 思路做有状态存储规划。

五、数据建模实战:设备上报时序

给每行加 TTL 可让冷数据自动过期,避免手动清理:

-- 写入时设置 30 天过期
INSERT INTO iot.device_metrics (device_id, bucket, ts, temperature)
VALUES ('dev-01', '2026-09-25', toTimestamp(now()), 36.5)
USING TTL 2592000;

-- 查询某设备最近 1 小时数据
SELECT ts, temperature FROM iot.device_metrics
WHERE device_id='dev-01' AND bucket='2026-09-25'
  AND ts > '2026-09-25 15:00:00+0000'
LIMIT 100;

注意:分区键决定数据分布,单分区不能无限膨胀,否则会出现”大分区”导致 GC 停顿与读延迟陡增。按天/按小时分桶(bucket)是经典解法。

六、运维:nodetool 与修复

# 集群健康总览
nodetool status

# 某表的统计:读写延迟、SSTable 数量、压实情况
nodetool cfstats iot.device_metrics

# 手动修复(运维窗口执行,推荐用 incremental repair)
nodetool repair --inc iot device_metrics

副本间可能因节点宕机产生不一致,定期 repair 是必修课;配合 GitHub Actions 把 repair 放进定时任务,比人工记得更稳。监控侧可把 ReadLatency、WriteLatency、PendingCompactions 接入 Prometheus。

七、与关系型数据库怎么选

维度CassandraPostgreSQL / MySQL
扩展方式加节点线性扩展分库分表或读副本
事务单行轻事务(LWT),无跨行事务完整 ACID
查询必须走主键/二级索引任意列 join、聚合
一致性可调,最终一致为主强一致
适用写多读少、海量时序强一致业务、报表

当你需要跨行事务、复杂 join、强一致报表时,PostgreSQL 慢查询优化 与 MySQL 主从复制 仍是更稳妥的选择;Cassandra 不替代它们,而是补上”海量写入高可用”这一块。和 Redis 缓存设计 搭配时,也别指望 Cassandra 当低延迟缓存——它定位是持久存储。

八、生产落地检查清单

  • 用 NetworkTopologyStrategy,副本数按机房规划,至少 3 节点起步;
  • 主键先按查询模式设计,分区键控制单分区大小(建议 < 100MB);
  • 一致性级别默认 LOCAL_QUORUM,跨城写入再评估;
  • 写路径开启 QUORUM、配合读修复,避免永久脏数据;
  • 定时 nodetool repair,并监控压实堆积与 GC 停顿;
  • 冷数据用 TTL 自动清理,分桶防止大分区。

九、压实策略与容量规划

SSTable 不可变,更新和删除都是”追加+墓碑(tombstone)”,真正的清理靠后台压实(Compaction)。选错压实策略会直接拖垮读写,三种常用策略要分清:

策略适用场景特点
STCS(SizeTiered)默认,写多读少的通用负载小 SSTable 滚动合并成大文件,写放大低
LCS(Leveled)读多、读放大敏感分层固定大小,读路径短,但写放大高
TWCS(TimeWindow)时序、带 TTL 的数据按时间窗口归档,过期窗口整段丢弃,几乎零无效压实

物联网、指标类场景强烈建议用 TWCS:每个时间窗口(如一天)独立压实,数据过期后整窗口被丢弃,不会反复重写历史数据。容量规划上,Cassandra 默认 num_tokens=256(vnode)让数据在节点间均匀打散;用 nodetool tablestats 观察各表待压实数据量与磁盘占用,提前规划扩容,而不是等磁盘写满。

十、客户端接入:CQL 驱动示例

应用侧用官方 CQL 驱动连接,注意连接池与一致性级别要在驱动层统一设置,而不是每次查询手写。下面是 Python 驱动的最小可运行示例:

from cassandra.cluster import Cluster
from cassandra.query import dict_factory

cluster = Cluster(
    contact_points=["cass1", "cass2", "cass3"],
    load_balancing_policy=None,   # 默认 TokenAware 策略,请求直发目标分区节点
)
session = cluster.connect("iot")
session.row_factory = dict_factory
session.default_consistency_level = "LOCAL_QUORUM"  # 驱动层统一一致性级别

rows = session.execute(
    "SELECT ts, temperature FROM device_metrics "
    "WHERE device_id=%(did)s AND bucket=%(bk)s LIMIT 10",
    {"did": "dev-01", "bk": "2026-09-25"},
)
for r in rows:
    print(r["ts"], r["temperature"])

驱动会自动做令牌感知路由,把请求发到持有目标分区的节点,减少 coordinator 转发跳数。生产环境务必配置重试策略与幂等写(用 IF NOT EXISTS 或轻事务 LWT 处理并发插入),并对批量写入使用 execute_async 做并发攒批,避免单线程串行把集群吞吐压瘪。

掌握以上要点,你就能把 Cassandra 用在该用的地方:让写入与可用性的天花板,由”加机器”而不是”改架构”来抬高。

上一篇 Velero 实战:Kubernetes 集群备份与灾难恢复
下一篇 Vibe Coding 实战:用自然语言驱动开发
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]