(专业系统化+生动类比+全流程实操+正反案例对比)
文档版本:V1.0 | 适用版本:Apache Cassandra 4.x | 面向人群:从0入门的初学者、需生产落地的开发/运维工程师
开篇:先搞懂「Cassandra 到底是什么?」
一句话生动定义
Cassandra 是一个「能扛住亿级流量、永远不宕机、随便加机器就能扩容的分布式NoSQL数据库」,是抖音、美团、Netflix、苹果等大厂用来存海量用户行为、订单、消息数据的核心数据库。
如果用你已经熟悉的快递站类比贯穿全文:
MySQL = 小区楼下的夫妻店超市,东西全,但人多了就挤不动,老板不在就关门
Redis = 家门口的快递自提柜,拿东西超快,但只能放小件
Cassandra = 全国连锁的大型智能快递站,有几百上千个快递柜,没有唯一的站长,任何一个柜子坏了都不影响全站,快递多了就加柜子,永远能接件、永远能取件。
专业严谨定义
Apache Cassandra 是一款去中心化、高可用、线性可扩展、面向列的分布式NoSQL数据库,专为海量结构化/半结构化数据的高并发写入、低延迟查询场景设计,无单点故障,支持跨数据中心部署,是目前互联网行业海量时序数据、用户行为数据的首选存储方案之一。
它的诞生背景
2007年,Facebook为了解决其收件箱系统的海量消息存储问题,结合了Google BigTable的数据模型和Amazon Dynamo的分布式架构,开发了Cassandra,2008年开源捐赠给Apache软件基金会,目前已成为Apache顶级项目,是分布式数据库领域的标杆产品。
第一章:Cassandra 核心特性:为什么大厂都用它?
每个特性都用「快递站类比+专业解释+适用场景」三维讲解,彻底搞懂它的核心价值。
第二章:Cassandra 核心设计原理:为什么它永远不宕机、写入这么快?
这一章是Cassandra的「内功心法」,搞懂底层原理,你就不会再用错,所有最佳实践都源于底层设计。我们依然用快递站类比,把复杂的分布式原理讲透。
2.1 去中心化架构:无主设计,彻底告别单点故障
传统数据库的痛点(主从架构)
MySQL的主从架构,就像快递站只有一个「站长柜」,所有快递必须先经过站长柜,再同步到其他柜子。站长柜坏了,全站就瘫痪了,必须先恢复站长柜才能正常营业——这就是单点故障。
Cassandra的无主架构
Cassandra集群里的所有节点完全平等,没有主从之分:
客户端可以连接任何一个节点(这个节点会成为本次请求的「协调节点」)
协调节点会根据数据的分区键,算出数据该存在哪些节点上,直接转发请求
任何一个节点宕机,协调节点会自动把请求转发给其他副本节点,业务完全无感知
生动总结:就像快递站的任何一个柜子都能收件、取件,哪怕一半柜子坏了,剩下的柜子依然能正常营业,永远不会关门。
2.2 一致性哈希:数据怎么均匀分布到所有节点?
这就是我们上一章讲的「魔法计算器」,也是分区键的底层核心原理,这里超级细腻地拆解全过程。
第一步:什么是哈希环?
我们把0~2³²的数字,首尾相连围成一个圆环(哈希环),集群里的每个节点,都会根据自己的IP/主机名算出一个哈希值,对应到哈希环上的一个位置。
就像我们把快递站的10个快递柜,按编号均匀摆放在一个圆形的跑道上,每个柜子占一个固定的位置。
第二步:数据怎么定位节点?
当你写入一行数据时:
Cassandra 会对这行数据的分区键做哈希计算,得到一个0~2³²之间的数字
拿着这个数字,在哈希环上顺时针找第一个遇到的节点,这个节点就是数据的「主副本节点」
按照你配置的副本数,继续顺时针找后续的节点,作为副本节点
快递站类比:
快递单号(分区键)放进魔法计算器,算出一个跑道上的位置
顺时针第一个遇到的快递柜,就是放这个快递的主柜子
再往后找2个柜子,放快递的备份,一共3个副本
第三步:虚拟节点:解决数据倾斜的终极方案
如果只用物理节点对应哈希环上的一个位置,会出现两个问题:
节点少的时候,数据分布不均匀,有的柜子快递堆成山,有的柜子空着
新增/下线节点时,需要迁移的数据量太大
Cassandra 用虚拟节点(Vnode) 解决了这个问题:
每个物理节点,不再对应哈希环上的一个位置,而是对应几百个虚拟节点,均匀散列在哈希环上
数据会均匀分布到所有虚拟节点上,最终均匀落到每个物理节点
新增/下线节点时,只会影响少量虚拟节点的数据迁移,对集群影响极小
生动总结:就像把一个大快递柜,拆成了256个小格子,均匀散落在圆形跑道上,快递会均匀分到每个小格子里,不会出现某个大柜子堆爆的情况。
2.3 副本机制:数据丢不了、服务停不了的核心
副本就是「数据的备份」,Cassandra的副本机制,是它高可用的核心保障。
核心概念
副本数(Replication Factor):一份数据要存几个备份,生产环境建议3副本,单机测试可以设1副本
副本策略:副本怎么分布到节点上,分为两种:
SimpleStrategy:简单策略,只在同一个数据中心里,按哈希环顺时针分布副本,仅单机/测试环境用NetworkTopologyStrategy:网络拓扑策略,可按数据中心配置副本数,副本会分散到不同机架、不同数据中心,生产环境必须用这个
生产级副本配置示例
-- 生产环境键空间配置:北京数据中心3副本,上海数据中心2副本
CREATE KEYSPACE IF NOT EXISTS order_db
WITH replication = {
'class': 'NetworkTopologyStrategy',
'BJ-DC': 3, -- 北京机房3副本,分散到3个不同机架
'SH-DC': 2 -- 上海机房2副本,做异地容灾
};快递站类比:北京的快递站,同一个快递放3个不同的快递柜,还同步2份到上海的快递站,哪怕北京的快递站全炸了,上海的站点还能取到快递。
2.4 读写流程:为什么Cassandra写入比读取快10倍?
这是Cassandra最核心的性能设计,也是很多初学者搞不懂的地方,我们用「快递收件流程」类比,一步一步讲透。
2.4.1 写入流程:永远的O(1)操作,无随机IO
快递员送快递的流程,对应Cassandra的写入流程,全程无等待、无随机IO,速度拉满:
核心结论:Cassandra的写入,只需要一次顺序磁盘写(WAL)+ 一次内存写,就可以返回成功,全程无随机IO,所以写入性能极致,哪怕每秒百万级写入,也能轻松扛住。
2.4.2 读取流程:怎么快速找到数据?
用户取快递的流程,对应Cassandra的读取流程,核心是「精准定位分区,合并多版本数据」:
客户端发送查询请求,必须带分区键,协调节点根据分区键算出数据所在的节点
协调节点根据配置的一致性级别,向对应节点发起读取请求
目标节点收到请求后,先查MemTable(内存),再查SSTable(磁盘),合并所有版本的数据,返回最新的结果
协调节点收到数据后,校验一致性,返回给客户端
为什么读取比写入慢?
读取需要查内存+多个SSTable,还要合并数据,甚至可能需要跨节点校验一致性,所以读取开销比写入大。这也是为什么Cassandra的设计理念是「查询驱动表设计」,必须用分区键精准定位数据,避免跨分区查询。
第三章:Cassandra 核心术语扫盲:从0到1搞懂所有概念
这一章把所有Cassandra的核心术语,用「快递站类比+专业定义+示例」三维讲解,彻底告别术语恐惧,所有术语前后统一,和之前的内容完全呼应。
第四章:核心中的核心:分区键 深度全解
这一章是整个文档的重中之重,结合之前的内容,超级详细、细腻地拆解分区键的所有知识点,从原理到实操,从避坑到最佳实践,彻底搞懂分区键。
4.1 什么是分区键?再一次讲透本质
一句话本质
分区键 = 数据的「地址编码」,是Cassandra决定数据存在哪个节点、哪个分区的唯一依据,也是你能快速查到数据的唯一钥匙。
没有分区键,Cassandra就不知道数据放哪个柜子,只能翻遍整个快递站的所有柜子(全表扫描),这在Cassandra里是绝对禁止的,会直接拖垮整个集群。
分区键在主键里的位置
Cassandra的主键定义,有固定的格式,分区键永远是主键的第一部分:
-- 格式1:简单主键 = 简单分区键(无聚类键)
PRIMARY KEY (分区键)
-- 格式2:复合主键 = 简单分区键 + 聚类键
PRIMARY KEY (分区键, 聚类键1, 聚类键2...)
-- 格式3:复合主键 = 复合分区键 + 聚类键
PRIMARY KEY ((分区键1, 分区键2...), 聚类键1, 聚类键2...)⚠️ 关键规则:
复合分区键必须用双括号括起来,否则Cassandra会把第一个字段当成分区键,后面的当成聚类键
查询时,必须带全分区键的所有字段,少一个都不行
聚类键是可选的,用于分区内排序和范围查询
4.2 分区键的完整工作流程
我们用「用户订单表」的例子,一步一步拆解分区键的工作全流程,细腻到每一个环节。
示例表定义
CREATE TABLE IF NOT EXISTS order_db.user_orders (
user_id UUID,
order_month TEXT,
order_time TIMESTAMP,
order_id UUID,
food_name TEXT,
price DECIMAL,
PRIMARY KEY ((user_id, order_month), order_time DESC)
);复合分区键:
(user_id, order_month)(用户ID+订单月份)聚类键:
order_time DESC(按下单时间降序排序)
写入时的分区键工作流程
你执行插入语句,写入小明2026年3月的订单:
INSERT INTO order_db.user_orders (user_id, order_month, order_time, order_id, food_name, price) VALUES ( '550e8400-e29b-41d4-a716-446655440000', '2026-03', '2026-03-27 12:00:00', uuid(), '香辣鸡腿堡', 25.0 );Cassandra 提取分区键的两个字段值:
user_id=xxx+order_month=2026-03,拼接后做Murmur3哈希计算(Cassandra默认的哈希算法),得到一个哈希值:123456789根据哈希值,在哈希环上顺时针找到第一个节点,作为主副本节点,再按副本数找到副本节点
把这行数据,写入对应节点的WAL和MemTable,最终落到SSTable里
同一个用户、同一个月的所有订单,都会算出同一个哈希值,永远存在同一个分区里
查询时的分区键工作流程
你执行查询语句,查小明2026年3月的所有订单:
SELECT * FROM order_db.user_orders WHERE user_id = '550e8400-e29b-41d4-a716-446655440000' AND order_month = '2026-03';Cassandra 提取分区键值,做哈希计算,得到和写入时一样的哈希值
123456789直接定位到数据所在的节点和分区,不用查其他任何节点
在分区内,按聚类键
order_time DESC的排序,直接返回数据,全程O(1)操作,毫秒级返回
错误查询的后果
如果你执行不带分区键的查询:
SELECT * FROM order_db.user_orders WHERE price > 20;Cassandra 不知道数据在哪个节点,只能向集群里的所有节点发送查询请求,每个节点都要扫描自己的全量数据,再把结果汇总返回——这就是全表扫描,会耗尽整个集群的CPU和IO资源,生产环境会直接导致集群雪崩。
4.3 分区键的两种类型:适用场景+代码示例
类型1:简单分区键(Single Partition Key)
只有一个字段作为分区键,结构最简单。
代码示例
-- 简单分区键:user_id,适合用户基础信息表,一个用户只有一行数据
CREATE TABLE IF NOT EXISTS order_db.user_info (
user_id UUID PRIMARY KEY, -- 简单分区键
username TEXT,
phone TEXT,
address TEXT,
create_time TIMESTAMP
);适用场景
单分区内的数据量很小(比如一行/几行数据)
查询场景固定,永远带这个分区键字段
数据不会随时间持续增长,不会出现大分区
优点
结构简单,查询灵活,只要带分区键就能查
不会出现查询时漏写分区键字段的问题
缺点
无法拆分大分区,如果分区内数据持续增长,会出现大分区问题
类型2:复合分区键(Composite Partition Key)
用多个字段拼接成一个分区键,必须用双括号括起来,是生产环境最常用的类型。
代码示例
-- 复合分区键:(user_id, order_month),适合订单表,按用户+月份拆分分区
CREATE TABLE IF NOT EXISTS order_db.user_orders (
user_id UUID,
order_month TEXT,
order_time TIMESTAMP,
order_id UUID,
food_name TEXT,
price DECIMAL,
PRIMARY KEY ((user_id, order_month), order_time DESC)
);适用场景
数据会随时间持续增长(比如订单、日志、时序数据)
单用户/单设备的数据量很大,需要拆分到多个分区
查询场景固定,会同时带分区键的所有字段(比如查用户某个月的订单)
优点
可以把大分区分成多个小分区,避免单分区过大
数据分布更均匀,不会出现数据倾斜
查询依然是单分区查询,性能极高
缺点
查询时必须带全分区键的所有字段,少一个都不行
无法跨分区做范围查询(比如查用户2026年1-3月的订单,需要查3个分区)
4.4 分区的黄金规则:单个分区不能超过100MB
这是Cassandra官方的硬性建议,也是生产环境最容易踩的坑,必须牢牢记住。
为什么不能超过100MB?
查询性能暴跌:分区太大,Cassandra扫描分区内数据的时间会变长,原本毫秒级的查询,会变成几百毫秒甚至几秒
节点恢复困难:节点宕机后,数据同步需要传输大分区,耗时极长,容易导致集群不一致
Compaction压力巨大:大分区的SSTable合并,会占用大量CPU和IO,影响集群稳定性
内存溢出风险:读取大分区时,会把大量数据加载到内存,容易导致OOM(内存溢出)
怎么计算分区大小?
举个例子:
一行订单数据,平均大小是200字节
一个用户一个月有1000条订单,单分区大小=200字节 * 1000 = 200KB(远小于100MB,安全)
一个用户一年有10万条订单,单分区大小=200字节 * 10万 = 20MB(依然安全)
一个用户一天有1万条订单,用用户ID做分区键,一年就有365万条,单分区大小=730MB(严重超标,必须拆分)
怎么拆分大分区?
核心思路:给分区键增加时间维度,按时间粒度拆分
第五章:CQL 实操全指南:从环境搭建到增删改查
这一章是保姆级实操,从环境搭建开始,一步步带你写CQL,所有代码都可以直接复制运行,同时标注正确和错误的用法。
5.1 环境搭建(5分钟搞定)
1. 下载安装
选择稳定版4.1.x,解压到本地目录即可,无需复杂安装
前置依赖:JDK 8/11(Cassandra 4.x推荐JDK11)
2. 启动Cassandra
Windows:双击
bin/cassandra.batLinux/Mac:在终端执行
bin/cassandra -f(-f表示前台运行)看到
Startup complete就说明启动成功了
3. 打开CQL命令行
执行
bin/cqlsh,就能进入Cassandra的命令行界面,开始写CQL了
5.2 核心CQL操作:从建库到增删改查
操作1:创建键空间(Keyspace)
键空间相当于MySQL的数据库,必须先建键空间,才能建表。
测试环境建库
-- 单机测试用,简单策略,1副本
CREATE KEYSPACE IF NOT EXISTS test_db
WITH replication = {
'class': 'SimpleStrategy',
'replication_factor': 1
}
AND DURABLE_WRITES = true; -- 开启持久化,默认true,关闭会丢数据
-- 切换到这个键空间
USE test_db;生产环境建库
-- 生产环境用,网络拓扑策略,多数据中心多副本
CREATE KEYSPACE IF NOT EXISTS order_db
WITH replication = {
'class': 'NetworkTopologyStrategy',
'BJ-DC': 3,
'SH-DC': 2
}
AND DURABLE_WRITES = true;操作2:创建表(Table)
建表的核心是设计主键、分区键、聚类键,必须遵循「查询驱动设计」,先想清楚查询场景,再建表。
示例1:用户信息表(简单分区键)
CREATE TABLE IF NOT EXISTS test_db.user_info (
user_id UUID,
username TEXT,
phone TEXT,
address TEXT,
create_time TIMESTAMP,
PRIMARY KEY (user_id) -- 简单分区键
)
WITH comment = '用户基础信息表'
AND gc_grace_seconds = 864000; -- 墓碑保留时间,默认10天示例2:用户订单表(复合分区键+聚类键)
CREATE TABLE IF NOT EXISTS test_db.user_orders (
user_id UUID,
order_month TEXT,
order_time TIMESTAMP,
order_id UUID,
food_name TEXT,
price DECIMAL,
pay_status INT,
PRIMARY KEY ((user_id, order_month), order_time DESC, order_id) -- 复合分区键+多聚类键
)
WITH comment = '用户订单表'
AND CLUSTERING ORDER BY (order_time DESC, order_id ASC); -- 聚类键的排序规则⚠️ 关键说明:
聚类键的排序规则,在建表时就定死了,查询时不能修改
多个聚类键,排序是按顺序的,先按第一个聚类键排,再按第二个排
操作3:插入数据(INSERT)
Cassandra的插入是「UPSERT」逻辑:如果主键已存在,就更新;不存在,就插入,不会报错。
-- 插入用户信息
INSERT INTO test_db.user_info (user_id, username, phone, address, create_time)
VALUES (
uuid(),
'小明',
'13800138000',
'北京市朝阳区',
toTimestamp(now())
);
-- 插入订单数据
INSERT INTO test_db.user_orders (user_id, order_month, order_time, order_id, food_name, price, pay_status)
VALUES (
550e8400-e29b-41d4-a716-446655440000,
'2026-03',
'2026-03-27 12:00:00',
uuid(),
'香辣鸡腿堡',
25.0,
1
);操作4:查询数据(SELECT)
查询的核心铁律:必须带分区键,禁止全表扫描,禁止滥用ALLOW FILTERING
✅ 正确查询:带全分区键
-- 查单个用户信息(带简单分区键)
SELECT * FROM test_db.user_info
WHERE user_id = 550e8400-e29b-41d4-a716-446655440000;
-- 查用户某个月的订单(带全复合分区键)
SELECT * FROM test_db.user_orders
WHERE user_id = 550e8400-e29b-41d4-a716-446655440000
AND order_month = '2026-03';
-- 分区内范围查询(带分区键+聚类键范围)
SELECT * FROM test_db.user_orders
WHERE user_id = 550e8400-e29b-41d4-a716-446655440000
AND order_month = '2026-03'
AND order_time >= '2026-03-01 00:00:00'
AND order_time <= '2026-03-31 23:59:59';❌ 错误查询:绝对禁止
-- 错误1:不带分区键,全表扫描
SELECT * FROM test_db.user_orders;
-- 错误2:只带复合分区键的一部分,无法定位分区
SELECT * FROM test_db.user_orders
WHERE user_id = 550e8400-e29b-41d4-a716-446655440000;
-- 错误3:滥用ALLOW FILTERING,本质还是全表扫描
SELECT * FROM test_db.user_orders
WHERE price > 20 ALLOW FILTERING;⚠️ 关于ALLOW FILTERING:
它允许Cassandra在查询时过滤非主键字段,但本质是先全表/全分区扫描,再过滤数据,只有在「分区内数据量很小,过滤后结果极少」的场景才能用,生产环境99%的场景都禁止使用。
操作5:更新数据(UPDATE)
更新必须带全主键,只能更新非主键字段,Cassandra没有「行级锁」,更新是最终一致性的。
-- 更新用户手机号,必须带主键user_id
UPDATE test_db.user_info
SET phone = '13900139000'
WHERE user_id = 550e8400-e29b-41d4-a716-446655440000;操作6:删除数据(DELETE)
Cassandra的删除不是真的立刻删除,而是写一个「墓碑(Tombstone)」标记,后台Compaction时才会真正清理。
-- 删除一行订单数据,必须带全主键
DELETE FROM test_db.user_orders
WHERE user_id = 550e8400-e29b-41d4-a716-446655440000
AND order_month = '2026-03'
AND order_time = '2026-03-27 12:00:00'
AND order_id = 123e4567-e89b-12d3-a456-426614174000;
-- 删除一个分区的所有数据
DELETE FROM test_db.user_orders
WHERE user_id = 550e8400-e29b-41d4-a716-446655440000
AND order_month = '2026-03';第六章:Cassandra 数据模型设计黄金法则
这是Cassandra和MySQL最核心的区别,也是90%的初学者用错Cassandra的根本原因。
6.1 核心设计理念:查询驱动设计,而非实体驱动设计
MySQL的设计思路(实体驱动)
先设计实体(用户、订单、商品),按第三范式建表,通过主键、外键关联,一个表可以应对多种查询场景,用JOIN关联数据。
Cassandra的设计思路(查询驱动)
一个查询场景,对应一张表,完全反范式,数据冗余存储,表结构完全为查询服务,不做JOIN,不做跨表关联。
生动类比:
MySQL = 图书馆,按图书分类放书,你可以按书名、作者、出版社多种方式找书,需要翻索引
Cassandra = 你家的书架,你会把「常看的编程书」放第一层,「睡前看的小说」放床头,「孩子的绘本」放儿童房——怎么方便拿,就怎么放,同一本书可以放多个地方,完全为你的使用场景服务。
6.2 数据模型设计5步走(生产级标准流程)
我们用「外卖订单系统」的例子,走一遍完整的设计流程。
第一步:梳理所有业务查询场景
先把所有需要的查询场景列出来,一个都不能漏,这是设计的基础:
用户端:查询「我的」某个月的所有订单
用户端:查询「我的」某个订单的详情
商家端:查询「我的店铺」某天的所有订单
运营端:查询某个城市某个时间段的订单统计
第二步:为每个查询场景设计一张表
一个查询场景对应一张表,数据冗余存储,完全为查询服务。
表1:用户订单表(对应用户查自己的月订单)
-- 查询场景:用户查自己某个月的订单,按下单时间倒序
CREATE TABLE IF NOT EXISTS order_db.user_orders_by_user_month (
user_id UUID,
order_month TEXT,
order_time TIMESTAMP,
order_id UUID,
shop_id UUID,
shop_name TEXT,
food_name TEXT,
price DECIMAL,
pay_status INT,
PRIMARY KEY ((user_id, order_month), order_time DESC, order_id)
);表2:订单详情表(对应用户查单个订单详情)
-- 查询场景:按订单ID查订单详情
CREATE TABLE IF NOT EXISTS order_db.order_detail_by_id (
order_id UUID PRIMARY KEY,
user_id UUID,
shop_id UUID,
shop_name TEXT,
food_name TEXT,
price DECIMAL,
pay_status INT,
address TEXT,
create_time TIMESTAMP
);表3:商家订单表(对应商家查某天的订单)
-- 查询场景:商家查自己店铺某天的订单
CREATE TABLE IF NOT EXISTS order_db.shop_orders_by_shop_day (
shop_id UUID,
order_date DATE,
order_time TIMESTAMP,
order_id UUID,
user_id UUID,
user_name TEXT,
food_name TEXT,
price DECIMAL,
pay_status INT,
PRIMARY KEY ((shop_id, order_date), order_time DESC, order_id)
);第三步:设计分区键,避免大分区和数据倾斜
表1:用
(user_id, order_month)做复合分区键,按月份拆分,避免单用户订单太多导致大分区表2:用
order_id做简单分区键,每个订单一行,无大分区风险表3:用
(shop_id, order_date)做复合分区键,按天拆分,避免大商家订单太多导致大分区
第四步:设计聚类键,满足排序和范围查询
所有表的聚类键都用
order_time DESC,满足最新订单排在最前面的业务需求增加
order_id作为最后一个聚类键,保证主键唯一性,避免同一时间下单的订单主键冲突
第五步:写入时同步更新所有表
当用户下单时,需要同时写入这3张表,保证数据一致性。Cassandra支持批处理(BATCH),可以保证同一个分区的多个写入操作原子性。
-- 下单时,批量写入3张表
BEGIN BATCH
-- 写入用户订单表
INSERT INTO order_db.user_orders_by_user_month (user_id, order_month, order_time, order_id, shop_id, shop_name, food_name, price, pay_status)
VALUES ('xxx', '2026-03', '2026-03-27 12:00:00', 'yyy', 'zzz', '肯德基', '香辣鸡腿堡', 25.0, 1);
-- 写入订单详情表
INSERT INTO order_db.order_detail_by_id (order_id, user_id, shop_id, shop_name, food_name, price, pay_status, address, create_time)
VALUES ('yyy', 'xxx', 'zzz', '肯德基', '香辣鸡腿堡', 25.0, 1, '北京市朝阳区', '2026-03-27 12:00:00');
-- 写入商家订单表
INSERT INTO order_db.shop_orders_by_shop_day (shop_id, order_date, order_time, order_id, user_id, user_name, food_name, price, pay_status)
VALUES ('zzz', '2026-03-27', '2026-03-27 12:00:00', 'yyy', 'xxx', '小明', '香辣鸡腿堡', 25.0, 1);
APPLY BATCH;6.3 数据模型设计的3个绝对禁止
禁止用MySQL的范式思维设计Cassandra表:不要试图做表关联、不要搞第三范式,Cassandra没有JOIN,关联查询必须在业务代码里做
禁止一张表应对多个查询场景:不要试图用一张表满足所有查询,否则必然会出现不带分区键的查询,性能极差
禁止过度冗余:虽然是反范式设计,但不要把无关的字段冗余到表里,只冗余查询需要的字段,避免数据量过大
第七章:最佳实践 VS 反面案例 全对比
这一章是生产环境避坑指南,每个点都有反面案例、问题分析、最佳实践,全是大厂踩过的坑,必须牢牢记住。
7.1 分区键设计最佳实践
7.2 数据模型设计最佳实践
7.3 读写操作最佳实践
7.4 生产环境运维最佳实践
集群规划:
生产环境节点数至少3个,副本数至少3个,分散到不同机架
跨数据中心部署,至少2个数据中心,做异地容灾
节点配置:推荐8核32G以上,SSD磁盘,Cassandra对磁盘IO要求极高
参数配置:
堆内存设置:最大不超过31GB,推荐8-16GB,避免大堆GC停顿
虚拟节点数:默认256个,无需修改,保证数据均匀分布
并发读写线程数:根据CPU核心数调整,避免线程过多导致上下文切换频繁
监控告警:
必须监控:节点状态、读写延迟、分区大小、Compaction队列、CPU/内存/磁盘IO
告警阈值:单分区超过100MB告警,读写延迟超过100ms告警,节点宕机立刻告警
数据清理:
给有生命周期的数据设置TTL(过期时间),自动清理过期数据,避免手动删除产生大量墓碑
定期执行nodetool repair,修复集群数据不一致,生产环境至少每周一次
第八章:生产环境常见问题排查指南
问题1:数据倾斜,某个节点CPU/IO使用率远高于其他节点
排查方法
执行
nodetool tablestats 键空间名.表名,看每个分区的大小,找到超大分区执行
nodetool ring,看每个节点的负载分布,是否不均匀
根因
分区键选择错误,用了低基数字段,导致数据集中在少数分区
没有拆分大分区,单分区数据量过大,集中在某个节点
解决方案
重新设计分区键,用高基数字段做分区键
用复合分区键拆分大分区,按时间粒度分散数据
调整虚拟节点数,让数据分布更均匀
问题2:读取延迟极高,查询超时
排查方法
开启慢查询日志,找到慢查询语句,看是否带分区键
执行
nodetool tablehistograms 键空间名.表名,看读写延迟分布检查分区大小,是否有超过100MB的大分区
根因
查询不带分区键,全表扫描
分区过大,扫描分区内数据耗时过长
SSTable数量过多,读取时需要查多个SSTable
解决方案
优化查询语句,必须带分区键
拆分大分区,控制单分区大小在100MB以内
调整Compaction策略,减少SSTable数量
问题3:节点频繁GC停顿,甚至OOM内存溢出
排查方法
查看GC日志,看GC停顿时间和频率
查看堆内存配置,是否超过31GB
查看是否有大分区查询、无限制SELECT *查询
根因
堆内存设置过大,导致Full GC停顿时间过长
大分区查询,一次性加载大量数据到内存
批量写入过大,导致MemTable频繁刷盘,内存占用过高
解决方案
调整堆内存到8-16GB,最大不超过31GB
优化查询,必须加LIMIT限制,禁止无限制查询
控制批量写入大小,单批次不超过5MB
第九章:总结:Cassandra 核心心法
Cassandra的核心优势:去中心化无单点故障、线性可扩展、极致写入性能、跨数据中心高可用,是海量高并发写入场景的首选
分区键是灵魂:分区键决定了数据的分布和查询性能,所有设计都要围绕分区键展开,所有查询必须带分区键
设计理念是查询驱动:一个查询场景对应一张表,反范式冗余存储,不要用MySQL的思维设计Cassandra
生产环境铁律:单分区不超过100MB,禁止全表扫描,禁止滥用ALLOW FILTERING,3副本起步,跨机架/数据中心部署
Cassandra的适用场景:用户行为埋点、物联网时序数据、订单/消息记录、日志存储等海量写入、固定模式查询的场景;不适合需要频繁更新、复杂关联查询、强事务的场景。
附录
附录1:Cassandra 常用数据类型对照表
附录2:常用nodetool运维命令
附录3:版本选型建议
生产环境优先选择 4.1.x稳定版,不推荐3.x以下的老旧版本
JDK版本:4.x推荐JDK11,3.x推荐JDK8
不要用最新的开发版,避免未发现的bug