Apache Cassandra
官页 - https://cassandra.apache.org/_/index.html
官中文档- https://cassandra.apache.ac.cn/doc/latest/cassandra/getting-started/index.html
Wiki(Cassandra) - https://zh.wikipedia.org/zh-cn/Cassandra
What is Apache Cassandra?
Apache Cassandra is an open source NoSQL distributed database trusted by thousands of companies for scalability and high availability without compromising performance. Linear scalability and proven fault-tolerance on commodity hardware or cloud infrastructure make it the perfect platform for mission-critical data.
Apache Cassandra 是一种开源的 NoSQL 分布式数据库,被数千家公司信赖,其可扩展性和高可用性性能得到了验证。即使在普通硬件或云基础设施上,它也能实现线性扩展,并且具有出色的容错能力,因此它是处理关键数据的理想选择。
Cassandra 它最初由Meta开发,用于改善电子邮件系统的搜索性能的简单格式数据,集Google BigTable的数据模型与Amazon Dynamo的完全分布式架构于一身。Facebook于2008将 Cassandra 开源。
Cassandra 被设计为分布式数据库管理系统 (DBMS),依赖于点对点架构。Cassandra 集群中的每个节点或存储部分数据的单个服务器都是平等的,数据分布在各个对等节点,而非集中存储,从而消除了单点故障(单一故障可能会迅速引发连锁反应)。通过这种设计,系统即使在计划内停机或突发变化期间,也能实现无缝复制、高效数据分发并保持持续服务。
核心特性
- 无主节点架构(Masterless):集群中每个节点角色平等,没有主从之分,通过 Gossip 协议进行节点间通信。
- 线性水平扩展:可随时向集群中添加新节点,无需停机,系统会自动通过一致哈希算法重新分配数据,实现负载均衡。
- 高可用与容错:数据自动在多个节点和数据中心间复制,即使部分节点或整个数据中心宕机,应用仍可正常运行。
- 高写入吞吐:存储引擎针对写入密集型场景深度优化,可轻松处理每秒百万级写入,非常适合日志、指标采集和传感器数据摄入。
- 宽列存储模型(Wide-Column Store) Schema 设计,同一表中的不同行可以拥有不同的列,适合处理动态或半结构化数据。
注意: 它也不支持传统 ACID 事务
大规模、高写入、低延迟、可扩展、高可用。适合的场景如:IoT 数据、设备遥测、用户行为日志、订单流水、消息存储
数据概念
RDBMS 和 Cassandra 之间的设计差异 - https://cassandra.apache.ac.cn/doc/latest/cassandra/developing/data-modeling/data-modeling_rdbms.html
下表列出了区分 Cassandra 的数据模型和 RDBMS 的数据模型的对比。
| RDBMS | Cassandra |
|---|---|
| RDBMS 处理结构化数据。 | Cassandra 处理非结构化数据。 |
| 它具有固定的模式。 | Cassandra 具有灵活的架构。 |
| 在 RDBMS 中,表是一个数组的数组。 (ROW x COLUMN) | 在 Cassandra 中,表是"嵌套的键值对"的列表。 (ROW x COLUMN 键 x COLUMN 值) |
| 数据库是包含与应用程序对应的数据的最外层容器。 | Keyspace 是包含与应用程序对应的数据的最外层容器。 |
| 表是数据库的实体。 | 表或列族是键空间的实体。 |
| Row 是 RDBMS 中的单个记录。 | Row 是 Cassandra 中的一个复制单元。 |
| 列表示关系的属性。 | Column 是 Cassandra 中的存储单元。 |
| RDBMS 支持外键的概念,连接。 | 关系是使用集合表示。 |
从 Cassandra 3.0 开始,CQL 全面普及后:在 CQL 中用 CREATE TABLE 创建的东西,底层就是列族同一个东西。 |
数据的层次
Cassandra 的核心数据层次可以理解为:
集群(Cluster)
│
├── 数据中心(Datacenter)
│
├── 节点(Node)
│
└── Keyspace
│
├── Table
│ │
│ ├── Row
│ │ └── Column
│ │
│ └── Row
│
└── Table
Cassandra数据库是为跨越多条主机共同工作,对用户呈现为一个整体的分布式系统设计的。Cassandra最外层容器被称为群集。Cassandra将集群中的节点组织成一个环(ring),然后把数据分配到集群中的节点(Node)上。
Cassandra 集群中的每个节点或存储部分数据的单个服务器都是平等的, 集群中的所有节点都是一样的
Keyspace(键空间)
https://www.w3cschool.cn/cassandra/cassandra_data_model.html
Keyspace 是 Cassandra 中数据的最外层逻辑容器 。它有点类似于关系型数据库中的:Database / Schema
创建键空间的语法
sql
CREATE KEYSPACE Keyspace name
WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 3};
如:
sql
CREATE KEYSPACE store
WITH replication = {
'class': 'SimpleStrategy',
'replication_factor': 3
};
Keyspace 本身并不直接存储业务数据,而是用来管理一组 Table,并定义这些数据在 Cassandra 集群中的复制策略。
Keyspace 最重要的配置:
副本因子(Replication Factor)
它是集群中将接收相同数据副本的计算机数。
副本放置策略 (Replica placement strategy)
它只是把副本放在介质中的策略。有简单策略(机架感知策略),网络拓扑策略(数据中心共享策略)等策略。
常见策略:
| 策略名 | 中文名 | 描述 |
|---|---|---|
| SimpleStrategy | 简单策略 | 适用于只有一个数据中心。为集群指定简单的副本因子(有几个副本) |
| NetworkTopologyStrategy | 网络拓扑策略 | 推荐方式,因为可以扩展到多数据中心,可以单独为每个数据中心设置 Replication Factor |
Table(表)
相当于关系数据库的表(Table) 以前叫 Column Family
| 关系表 | Cassandra Table |
|---|---|
| 关系模型中的模式是固定的。 一旦为表定义了某些列,在插入数据时,在每一行中,所有列必须至少填充一个空值。 | 在 Cassandra 中,虽然定义了列族,但列不是。 您可以随时向任何列族自由添加任何列。 |
| 关系表只定义列,用户用值填充表。 | 在 Cassandra 中,表包含列,或者可以定义为超级列族。 |
https://cassandra.apache.ac.cn/doc/latest/cassandra/reference/cql-commands/create-table.html
可以理解类似 Java 的Map<String,Map<byte[],Column>> 结构存储, 下为表中的数据示例
!\[Pasted image 20260826214432.png]
Cassandra Table 中的每一行都用 Row Key 来标识,这个相当于关系数据库表中的主键,并且总是被索引的。
主键
PRIMARY KEY 由表中定义的一个或多个列组成。主键定义中列的顺序定义了分区键和聚簇列。
CQL 主键由两部分组成
- 分区键
- 它是主键定义的第一个组件。它可以是单个列,也可以使用额外的括号,可以是多个列。表必须至少有一个分区键,最小的表定义是
cql
CREATE TABLE t (k text PRIMARY KEY);
- 聚簇列
-
这些列是主键定义中分区键后面的列。这些列的顺序定义了_聚簇顺序_。
-
PRIMARY KEY (a):a是单个分区键,没有聚簇列 -
PRIMARY KEY (a, b, c):a是单个分区键,b和c是聚簇列 -
PRIMARY KEY ((a, b), c):a和b组成_复合_分区键,c是聚簇列
Column(列)
列是 Cassandra 的基本数据结构,是最小的存储单元, 具有三个值: 列名 + 列值 + 时间戳
| 名称 | 值 | 时间戳 |
|---|---|---|
| name:byte\[\] | value:byte\[\] | clock:byte\[\] |
元数据的时间戳
注意这里的时间戳是 Cassandra 列的内部元数据,由系统自动管理,不体现在表结构中,可以通过 writetime() 函数查询。
sql
-- 查看某列的写入时间戳(返回微秒级整数)
SELECT name, writetime(name) FROM users WHERE id = 1;
-- 同时查看多列的时间戳
SELECT name, age, writetime(name), writetime(age) FROM users WHERE id = 1;
- 分布式环境下多个节点同时写入同一列,时间戳最大的值胜出,保证最终一致性。
- 每次写入(INSERT / UPDATE)时,Cassandra 会为每个被修改的列 自动生成一个微秒级 时间戳,作为该列值的"版本号"。它不是表中的一个普通列,而是列的内部元数据,和列名、列值绑定在一起存储在 SSTable 中
- 主键列没有 writetime :
writetime()不能用于主键列,否则会报错
Cassandra 的一切操作(增、改、删)本质上都是"带时间戳的写入",读取时按时间戳合并取最新版本,过期的数据由后台 Compaction 清理。
Primary Key 和 Row Key
Row Key 是逻辑概念(标识一行数据),Primary Key 是物理概念(决定数据分布和排序)。当 Primary Key 只有一个字段时,它既是 Partition Key 也是 Row Key。
sql
CREATE TABLE orders (
user_id INT,
created_at TIMESTAMP,
amount DECIMAL,
PRIMARY KEY (user_id, created_at)
);
-- =========================================================
-- PRIMARY KEY 属性说明
-- =========================================================
-- PRIMARY KEY ((partition_key), clustering_column)
-- 第一部分:
-- Partition Key
-- 决定数据存储在哪个节点
--
-- 第二部分:
-- Clustering Column
-- 决定同一个 Partition 内的数据排序方式
- Partition Key =
user_id(决定数据在哪个节点) - Clustering Key =
created_at(决定分区内排序) - Primary Key =
(user_id, created_at)(全局唯一) - Row Key =
user_id(定位到分区,分区内再用created_at区分不同行)
**Cassandra会对Partition key 列计算出一个哈希值,该哈希值定义了分区位置。再例如:
cql
CREATE TABLE t (
a int,
b int,
c int,
d int,
PRIMARY KEY ((a, b), c, d)
);
INSERT INTO t (a, b, c, d) VALUES (0,0,0,0);
INSERT INTO t (a, b, c, d) VALUES (0,0,1,1);
INSERT INTO t (a, b, c, d) VALUES (0,1,2,2);
INSERT INTO t (a, b, c, d) VALUES (0,1,3,3);
INSERT INTO t (a, b, c, d) VALUES (1,1,4,4);
SELECT * FROM t;
cql
a | b | c | d
---+---+---+---
0 | 0 | 0 | 0
0 | 0 | 1 | 1
0 | 1 | 2 | 2
0 | 1 | 3 | 3
1 | 1 | 4 | 4
(5 rows)
| 数据说明 |
|---|
行 1 和 2 在同一分区中,因为列 a 和 b 都为零。 |
行 3 和 4 在同一分区中,但分区不同,因为列 a 为零,列 b 在两行中都为 1。 |
行 5 独自在一个第三个分区中,因为列 a 和 b 都为 1。 |
Get Started
https://cassandra.apache.org/doc/latest/cassandra/getting-started/index.html
Installing Cassandra
Apache Cassandra can be installed on a number of Linux distributions:
- AlmaLinux
- Amazon Linux Amazon Machine Images (AMIs)
- Debian
- RedHat Enterprise Linux (RHEL)
- SUSE Enterprise Linux
- Ubuntu
Install the latest version of Java 11 or Java 17, from one of the following locations
Unpack the tarball:
shell
$ tar xzvf apache-cassandra-4.0.0-bin.tar.gz
-
The files will be extracted to the
apache-cassandra-4.0.0/directory. This is the tarball installation location. -
Located in the tarball installation location are the directories for the scripts, binaries, utilities, configuration, data and log files:
plaintext
<tarball_installation>/
bin/ 1
conf/ 2
data/ 3
doc/
interface/
javadoc/
lib/
logs/ 4
pylib/
tools/ 5
- 运行Cassandra、cqlsh、nodetool以及SSTable工具的命令所在位置
- cassandra.yaml 及其他配置文件的路径
- 提交日志、提示信息和SSTable的存储位置
- 系统日志和调试日志的存储位置
- cassandra-stress工具的安装位置
Configuring Cassandra
https://cassandra.apache.org/doc/latest/cassandra/getting-started/configuring.html
The Cassandra configuration files location varies, depending on the type of installation:
- docker:
/etc/cassandradirectory - tarball:
confdirectory within the tarball install location - package:
/etc/cassandradirectory
Cassandra's default configuration file, cassandra.yaml, is sufficient to explore a simple single-node cluster.
cassandra.yaml:Cassandra的主要配置文件,其中包含敏感设置,因此不应被不可信任的用户访问或修改。cassandra-env.sh:可以设置环境变量文件。cassandra-rackdc.properties或cassandra-topology.properties:设置集群的机架和数据中心信息。logback.xml:日志配置文件,包含日志级别设置。jvm-*:多个JVM配置文件,用于服务器和客户端的行为设置。commitlog_archiving.properties:设置commitlog的归档参数。cqlshrc.sample:如何配置 CQL shell 工具 cqlsh
Main runtime properties
Configuring Cassandra is done by setting yaml properties in the cassandra.yaml file. At a minimum you should consider setting the following properties:
cluster_name: Set the name of your cluster.seeds: A comma separated list of the IP addresses of your clusterseed nodes.storage_port: Check that you don't have the default port of 7000 blocked by a firewall.listen_address: Thelisten addressis the IP address of a node that allows it to communicate with other nodes in the cluster. Set to localhost by default. Alternatively, you can setlisten_interfaceto tell Cassandra which interface to use, and consecutively which address to use. Set one property, not both.native_transport_port: Check that you don't have the default port of 9042 blocked by a firewall, so that clients like cqlsh can communicate with Cassandra on this port.
数据存储目录配置
Cassandra 的持久化数据主要涉及 SSTable 数据、Commit Log 和 Hints 三类,其存储位置均可通过 cassandra.yaml 进行配置。此外,Cassandra 还可以通过 saved_caches_directory 配置缓存文件的存储位置。
yaml
# 1. SSTable
data_file_directories:
- /var/lib/cassandra/data
# 2. CommitLog
commitlog_directory: /var/lib/cassandra/commitlog
# 3. Hints
hints_directory: /var/lib/cassandra/hints
# 4. Cache
saved_caches_directory: /var/lib/cassandra/saved_caches
| 配置 | 存储内容 | 核心作用 |
|---|---|---|
data_file_directories |
SSTable | 存储真正的数据 |
commitlog_directory |
Commit Log | 写入日志、故障恢复 |
hints_directory |
Hints | 节点不可用时的数据补偿 |
saved_caches_directory |
Cache | 保存缓存、提高性能 |
1. data_file_directories
data_file_directories 用于存储 Cassandra 真正的数据文件,主要是 SSTable 。Cassandra 会按照 Keyspace 和 Table 对数据进行组织,每个 Keyspace 通常对应数据目录下的一个目录,例如 ks1、ks2。同时还会存在 system、system_schema、system_auth 等 Cassandra 内部使用的系统 Keyspace。
该配置支持指定多个目录,例如将不同目录放在不同磁盘上。这样 Cassandra 可以利用多个磁盘的存储空间和 I/O 能力,提高整体存储容量和性能。
2. commitlog_directory
commitlog_directory 用于存储 Commit Log(提交日志)。Cassandra 在处理写请求时,会先将写操作记录到 Commit Log,然后写入 MemTable,之后 MemTable 再 Flush 成 SSTable。Commit Log 主要用于 Cassandra 节点发生故障后的数据恢复。
如果服务器有多个磁盘,可以将 Commit Log 放在与 data_file_directories 不同的磁盘上,从而减少 Commit Log 与 SSTable 数据文件之间的 I/O 竞争。不过是否需要独立磁盘,应根据实际硬件和负载情况决定。
3. hints_directory
hints_directory 用于存储 Hints。在 Cassandra 集群中,如果某个负责数据副本的节点暂时不可用,其他节点可以保存针对该节点的 Hint,等目标节点恢复后,再将 Hint 中记录的数据发送给它,从而帮助恢复数据副本的一致性。
Hints 是 Cassandra 分布式数据复制机制中的一种辅助数据,并不是业务数据的最终存储位置。它与 Commit Log 不同:Commit Log 主要用于本节点故障后的写入恢复 ,而 Hint 主要用于其他副本节点暂时不可用时的数据补偿。
4. saved_caches_directory
saved_caches_directory 用于存储 Cassandra 保存到磁盘中的缓存数据。这些缓存主要用于提高 Cassandra 启动和运行过程中的性能,例如保存部分已经建立的缓存信息,避免每次启动都完全重新建立。
缓存并不是业务数据的最终存储位置,因此即使缓存文件丢失,Cassandra 仍然可以重新建立缓存,不会像删除 SSTable 那样直接导致业务数据丢失。它属于性能优化相关的存储目录。
**in short: SSTable 存数据,Commit Log 保恢复,Hints 保副本,Cache 保性能。
认证配置
cassandra.yaml 找到:
authenticator: AllowAllAuthenticator
修改为:
authenticator: PasswordAuthenticator
| 配置 | 作用 |
|---|---|
PasswordAuthenticator |
强制客户端提供用户名/密码 |
CassandraAuthorizer |
控制用户能访问哪些 Keyspace、Table |
AllowAllAuthenticator |
不进行用户名密码认证 |
AllowAllAuthorizer |
不进行权限控制 |
如果是AllowAllAuthenticator: 则不需要用户名密码 直接 $ bin/cqlsh 可以裸奔登录。 |
使用默认超级用户登录
开启认证后,Cassandra 会自动创建默认超级用户 cassandra,密码也是 cassandra:
cqlsh -u cassandra -p cassandra
**修改 cassandra 用户密码
ALTER ROLE cassandra WITH PASSWORD = '新的强密码';
网络配置
sh
# ============================================================
# Cassandra 网络、端口、Listen Address 配置
# ============================================================
# ============================================================
# Listen Address
# ============================================================
#
# Cassandra 用哪个本机 IP 地址作为**节点间通信地址** 主要用于 Cassandra 节点之间的通信
# 注意这个改了, seed_provider.parameters.seeds: "192.168.1.101:7000" 也需要修改!!
#
listen_address: 192.168.1.101
# ============================================================
# RPC Address
# ============================================================
# 客户端连接 Cassandra 时使用的地址
#
#
rpc_address: 0.0.0.0
# ============================================================
# Native Transport
# ============================================================
# CQL 原生协议端口
#
# cqlsh、Java Driver、Python Driver 等客户端使用
native_transport_port: 9042
# 是否启用 Native Transport
native_transport: true
# Native Transport SSL 端口
#
# 启用客户端 SSL 后使用
native_transport_port_ssl: 9142
# ============================================================
# Storage Port
# ============================================================
# Cassandra 节点之间普通通信端口
# Gossip、数据传输等内部通信使用
storage_port: 7000
# 节点之间 SSL 加密通信
ssl_storage_port: 7001
# ============================================================
# JMX Port
# ============================================================
# nodetool 默认通过 JMX 管理 Cassandra
#
# 默认:
jmx_port: 7199
各个端口
启动它会占用N个端口
| 端口 | 用途 | 通信方向 | 配置文件 |
|---|---|---|---|
| 7000 | 节点间通信(Gossip、数据流、副本同步) | 节点 ↔ 节点 | cassandra.yaml |
| 7001 | 节点间通信(SSL 加密) | 节点 ↔ 节点 | cassandra.yaml |
| 9042 | 客户端连接(CQL Native Protocol) | 客户端 → 节点 | cassandra.yaml |
| 9142 | 客户端连接(SSL 加密) | 客户端 → 节点 | cassandra.yaml |
| 7199 | JMX 监控管理(nodetool 等工具使用) | 管理工具 → 节点 | cassandra-env.sh |
JDK版本配置
只能去改它的启动脚本 /opt/apache-cassandra-5.0.9/bin/cassandra
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PATH="$JAVA_HOME/bin:$PATH"
Start Cassandra 走你~
- 启动
shell
bin/cassandra
-
停止
./bin/nodetool stopdaemon
-
查看日志
tail -f logs/system.log
-
Check the status of Cassandra:
shell
bin/nodetool status
-
cqlsh登录
./bin/cqlsh 127.0.0.1 9042 -u cassandra
修改密码
ALTER ROLE cassandra WITH PASSWORD = 'BibYOlBIFshAWdCz';
数据类型
https://cassandra.apache.org/doc/4.1/cassandra/cql/types.html
The following table gives additional informations on the native data types, and on which kind of constants each type supports:
| Type | Constants supported | Description |
|---|---|---|
ascii |
string |
ASCII character string |
bigint |
integer |
64-bit signed long |
blob |
blob |
Arbitrary bytes (no validation) |
boolean |
boolean |
Either true or false |
counter |
integer |
Counter column (64-bit signed value). See counters for details. |
date |
integer, string |
A date (with no corresponding time value). See dates below for details. |
decimal |
integer, float |
Variable-precision decimal |
double |
integer float |
64-bit IEEE-754 floating point |
duration |
duration, |
A duration with nanosecond precision. See durations below for details. |
float |
integer, float |
32-bit IEEE-754 floating point |
inet |
string |
An IP address, either IPv4 (4 bytes long) or IPv6 (16 bytes long). Note that there is no inet constant, IP address should be input as strings. |
int |
integer |
32-bit signed int |
smallint |
integer |
16-bit signed int |
text |
string |
UTF8 encoded string |
time |
integer, string |
A time (with no corresponding date value) with nanosecond precision. See times below for details. |
timestamp |
integer, string |
A timestamp (date and time) with millisecond precision. See timestamps below for details. |
timeuuid |
uuid |
Version 1 UUID, generally used as a "conflict-free" timestamp. Also see timeuuid-functions. |
tinyint |
integer |
8-bit signed int |
uuid |
uuid |
A UUID (of any version) |
varchar |
string |
UTF8 encoded string |
varint |
integer |
Arbitrary-precision integer |
Maps
A map is a (sorted) set of key-value pairs, where keys are unique and the map is sorted by its keys. You can define and insert a map with:
cql
CREATE TABLE users (
id text PRIMARY KEY,
name text,
favs map<text, text> // A map of text keys, and text values
);
INSERT INTO users (id, name, favs)
VALUES ('jsmith', 'John Smith', { 'fruit' : 'Apple', 'band' : 'Beatles' });
// Replace the existing map entirely.
UPDATE users SET favs = { 'fruit' : 'Banana' } WHERE id = 'jsmith';
Sets
A set is a (sorted) collection of unique values. You can define and insert a map with:
cql
CREATE TABLE images (
name text PRIMARY KEY,
owner text,
tags set<text> // A set of text values
);
INSERT INTO images (name, owner, tags)
VALUES ('cat.jpg', 'jsmith', { 'pet', 'cute' });
// Replace the existing set entirely
UPDATE images SET tags = { 'kitten', 'cat', 'lol' } WHERE name = 'cat.jpg';
Lists
A list is a (sorted) collection of non-unique values where elements are ordered by there position in the list. You can define and insert a list with:
cql
CREATE TABLE plays (
id text PRIMARY KEY,
game text,
players int,
scores list<int> // A list of integers
)
INSERT INTO plays (id, game, players, scores)
VALUES ('123-afde', 'quake', 3, [17, 4, 2]);
// Replace the existing list entirely
UPDATE plays SET scores = [ 3, 9, 4] WHERE id = '123-afde';
对于时间日期的数据处理
Values of the
timestamptype are encoded as 64-bit signed integers representing a number of milliseconds since the standard base time known as the epoch: January 1 1970 at 00:00:00 GMT.
Timestamps can be input in CQL either using their value as aninteger, or using astringthat represents an ISO 8601 date. For instance, all of the values below are validtimestampvalues for Mar 2, 2011, at 04:05:00 AM, GMT:
1299038700000'2011-02-03 04:05+0000''2011-02-03 04:05:00+0000''2011-02-03 04:05:00.000+0000''2011-02-03T04:05+0000''2011-02-03T04:05:00+0000''2011-02-03T04:05:00.000+0000'
**Date type
a date can be input either as an
integeror using a datestring. In the later case, the format should beyyyy-mm-dd(so'2011-02-03'for instance).
**Time
Values of the
timetype are encoded as 64-bit signed integers representing the number of nanoseconds since midnight.
a time can be input either as anintegeror using astringrepresenting the time. In the later case, the format should behh:mm:ss[.fffffffff](where the sub-second precision is optional and if provided, can be less than the nanosecond). So for instance, the following are valid inputs for a time:
'08:12:54''08:12:54.123''08:12:54.123456''08:12:54.123456789'
对JSON的支持
https://cassandra.apache.ac.cn/doc/5.0/cassandra/developing/cql/json.html
Cassandra 2.2 在 SELECT <select-statement> 和 INSERT <insert-statement> 语句中引入了 JSON 支持。此支持不会从根本上改变 CQL API。它只是提供了一种方便的方式来处理 JSON 文档。
SELECT JSON
对于 SELECT 语句,JSON 关键字用于将每行返回为单个 JSON 编码的映射。SELECT 语句行为的其余部分保持不变。
结果映射键与普通结果集中的列名匹配。例如,类似于 SELECT JSON a, ttl(b) FROM ... 的语句将生成一个具有键 "a" 和 "ttl(b)" 的映射。但是,有一个值得注意的例外:为了与 INSERT JSON 行为对称,区分大小写的列名(包含大写字母)将用双引号括起来。例如,SELECT JSON myColumn FROM ... 将生成一个具有转义引号的映射键 "\"myColumn\""。
INSERT JSON
对于 INSERT 语句,新的 JSON 关键字可用于启用将 JSON 编码的映射作为单行插入。JSON 映射的格式通常应与在同一表上执行 SELECT JSON 语句返回的格式匹配。特别是,区分大小写的列名应使用双引号括起来。
下表描述了 Cassandra 在 INSERT JSON 值(和 from_json() 参数)中将接受的编码以及 Cassandra 在返回 SELECT JSON 语句(和 from_json())的数据时将使用的格式
| 类型 | 接受的格式 | 返回格式 | 说明 |
|---|---|---|---|
ascii |
字符串 | 字符串 | 使用 JSON 的 \u 字符转义 |
bigint |
整数、字符串 | 整数 | 字符串必须是有效的 64 位整数 |
blob |
字符串 | 字符串 | 字符串应为 0x 后跟偶数个十六进制数字 |
boolean |
布尔值、字符串 | boolean | 字符串必须是 "true" 或 "false" |
date |
字符串 | 字符串 | 格式为 YYYY-MM-DD 的日期,时区为 UTC |
decimal |
整数、浮点数、字符串 | 浮点数 | 可能超过客户端解码器中的 32 位或 64 位 IEEE-754 浮点数精度 |
double |
整数、浮点数、字符串 | 浮点数 | 字符串必须是有效的整数或浮点数 |
浮点数 |
整数、浮点数、字符串 | 浮点数 | 字符串必须是有效的整数或浮点数 |
inet |
字符串 | 字符串 | IPv4 或 IPv6 地址 |
int |
整数、字符串 | 整数 | 字符串必须是有效的 32 位整数 |
list |
列表、字符串 | list | 使用 JSON 的本机列表表示形式 |
map |
映射、字符串 | map | 使用 JSON 的本机映射表示形式 |
smallint |
整数、字符串 | 整数 | 字符串必须是有效的 16 位整数 |
set |
列表、字符串 | list | 使用 JSON 的本机列表表示形式 |
text |
字符串 | 字符串 | 使用 JSON 的 \u 字符转义 |
time |
字符串 | 字符串 | 格式为 HH-MM-SS[.fffffffff] 的一天中的时间 |
timestamp |
整数、字符串 | 字符串 | 时间戳。字符串常量允许输入 timestamps as dates <timestamps>。格式为 YYYY-MM-DD HH:MM:SS.SSS 的日期戳将被返回。 |
timeuuid |
字符串 | 字符串 | 类型 1 UUID。有关 UUID 格式,请参见 constant |
tinyint |
整数、字符串 | 整数 | 字符串必须是有效的 8 位整数 |
tuple |
列表、字符串 | list | 使用 JSON 的本机列表表示形式 |
UDT |
映射、字符串 | map | 使用 JSON 的本机映射表示形式,字段名称作为键 |
uuid |
字符串 | 字符串 | 有关 UUID 格式,请参见 constant |
varchar |
字符串 | 字符串 | 使用 JSON 的 \u 字符转义 |
varint |
整数、字符串 | 整数 | 可变长度;可能在客户端解码器中溢出 32 位或 64 位整数 |
JSON 转换函数
-
from_json() 函数
from_json()函数的使用方式类似于INSERT JSON,但用于单个列值。它只能在INSERT语句的VALUES子句中使用,或者作为UPDATE、DELETE或SELECT语句中的列值之一使用。例如,它不能在SELECT语句的选择子句中使用。 -
to_json() 函数
to_json()函数的使用方式类似于SELECT JSON,但用于单个列值。它只能在SELECT语句的选择子句中使用。
用户和授权
c
// 修改system_auth 避免单点故障
ALTER KEYSPACE system_auth WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 };
// 创建带有超级权限的用户
CREATE ROLE cassandra_dba WITH SUPERUSER = true AND LOGIN = true AND PASSWORD = 'BibYOlBIFshAWdCz';
// 禁用默认账号
ALTER ROLE cassandra WITH SUPERUSER = false AND LOGIN = false;
用户管理
sql
-- 创建超级用户
CREATE USER admin
WITH PASSWORD 'Admin@123456'
SUPERUSER;
-- 创建普通用户
CREATE USER app
WITH PASSWORD 'App@123456'
NOSUPERUSER;
-- 创建只读用户
CREATE USER readonly
WITH PASSWORD 'ReadOnly@123456'
NOSUPERUSER;
-- =========================================================
-- 用户管理
-- =========================================================
-- 修改密码
ALTER USER app
WITH PASSWORD 'NewPassword@123456';
-- 设置为超级用户
ALTER USER app SUPERUSER;
-- 取消超级用户
ALTER USER app NOSUPERUSER;
-- 查看所有用户
LIST USERS;
-- 删除用户
DROP USER readonly;
权限管理
sql
-- =========================================================
-- 创建 Role
-- =========================================================
-- 创建业务 Role
CREATE ROLE app_role;
-- 创建只读 Role
CREATE ROLE readonly_role;
-- =========================================================
-- 授权
-- =========================================================
-- app_role:允许查询和修改 myapp
GRANT SELECT, MODIFY
ON KEYSPACE myapp
TO app_role;
-- readonly_role:只允许查询 myapp
GRANT SELECT
ON KEYSPACE myapp
TO readonly_role;
-- 将 Role 授予用户
GRANT app_role
TO app;
GRANT readonly_role
TO readonly;
-- =========================================================
-- Table 级别授权
-- =========================================================
-- 只允许访问指定 Table
GRANT SELECT, MODIFY
ON TABLE myapp.user
TO app;
GRANT SELECT
ON TABLE myapp.user
TO readonly;
-- =========================================================
-- 查看权限
-- =========================================================
-- 查看用户拥有的所有权限
LIST ALL PERMISSIONS OF app;
-- 查看 Role 权限
LIST ALL PERMISSIONS OF app_role;
-- 查看用户拥有的 Role
LIST ROLES OF app;
-- =========================================================
-- 撤销权限
-- =========================================================
REVOKE MODIFY
ON KEYSPACE myapp
FROM app_role;
REVOKE SELECT
ON KEYSPACE myapp
FROM readonly_role;
-- =========================================================
-- 删除 Role
-- =========================================================
DROP ROLE readonly_role;
DLL 管理
CQL
https://cassandra.apache.ac.cn/doc/5.0/cassandra/developing/cql/index.html
SQL
-- Describe cluster 提供有关集群的信息
Describe cluster
-- 显示当前Cassandra里的所有键空间
Describe Keyspaces
-- 列出键空间的所有表
Describe tables;
--切换到 键空间 和 mysql 一样
USE system_trace;
Keyspace
键空间和表名应仅包含字母数字字符,不能为空,大小限制为 48 个字符(此限制主要用于避免文件名(可能包含键空间和表名)超过某些文件系统的限制)。默认情况下,键空间和表名不区分大小写(myTable 等同于 mytable),但可以使用双引号强制区分大小写("myTable" 与 mytable 不同)。
此外,表始终是键空间的一部分,表名可以通过其所属的键空间进行完全限定。如果未进行完全限定,则假定该表位于_当前_键空间中
| 指令 | 描述 |
|---|---|
| CREATE KEYSPACE | 在Cassandra中创建KeySpace |
| USE | 连接到已创建的KeySpace |
| ALTER KEYSPACE | 更改KeySpace的属性 |
| DROP KEYSPACE | 删除KeySpace |
| CREATE TABLE | 在KeySpace中创建表 |
sql
-- =========================================================
-- 1. Keyspace
-- =========================================================
-- 创建 Keyspace
CREATE KEYSPACE myapp
WITH replication = {
'class': 'NetworkTopologyStrategy',
'datacenter1': 3
};
-- 单机测试
CREATE KEYSPACE test
WITH replication = {
'class': 'SimpleStrategy',
'replication_factor': 1
};
-- 查看 Keyspace
DESCRIBE KEYSPACE myapp;
-- 删除 Keyspace
DROP KEYSPACE myapp;
-- 修改 Keyspace 属性
ALTER KEYSPACE myapp
WITH replication = {
'class': 'NetworkTopologyStrategy',
'datacenter1': 3
};
-- =========================================================
-- Keyspace 常见属性
-- =========================================================
-- replication
-- 副本配置
--
-- class
-- SimpleStrategy
-- 单机/简单测试使用
--
-- NetworkTopologyStrategy
-- 生产环境推荐
--
-- replication_factor
-- SimpleStrategy 使用
--
-- datacenter1: 3
-- 表示 datacenter1 保存 3 个副本
--
-- durable_writes
-- 是否启用 CommitLog
--
-- 默认:true
--
-- 例如:
CREATE KEYSPACE demo
WITH replication = {
'class': 'NetworkTopologyStrategy',
'datacenter1': 3
}
AND durable_writes = true;
-- =========================================================
-- 查看 Schema
-- =========================================================
-- 查看所有 Keyspace
DESCRIBE KEYSPACES;
-- 查看当前 Keyspace
DESCRIBE KEYSPACE myapp;
-- 查看所有 Table
DESCRIBE TABLES;
-- 查看指定 Table
DESCRIBE TABLE myapp.user;
-- 查看完整 Schema
DESCRIBE SCHEMA;
Table
| ALTER TABLE | 修改表的列属性 |
|---|---|
| DROP TABLE | 删除表 |
| TRUNCATE | 从表中删除所有数据 |
| CREATE INDEX | 在表的单个列上定义新索引 |
| DROP INDEX | 删除命名索引 |
sql
-- =========================================================
-- 2. Table
-- =========================================================
-- 创建基本 Table
CREATE TABLE myapp.user (
id uuid PRIMARY KEY,
username text,
age int,
email text
);
create table book(
id int,
name varchar,
type varchar,
no int,
PRIMARY KEY(id)
);
-- 创建复合主键
CREATE TABLE myapp.device (
device_id text,
create_time timestamp,
name text,
status int,
PRIMARY KEY (device_id, create_time)
);
-- 创建复合 Partition Key
CREATE TABLE myapp.device_data (
device_id text,
device_type text,
create_time timestamp,
value double,
PRIMARY KEY ((device_id, device_type), create_time)
);
-- =========================================================
-- Table 常见操作
-- =========================================================
-- 查看表结构
DESCRIBE TABLE myapp.user;
-- 删除 Table
DROP TABLE myapp.user;
-- 修改 Table
ALTER TABLE myapp.user
ADD phone text;
-- 修改列类型
ALTER TABLE myapp.user
ALTER age TYPE bigint;
-- 删除列
ALTER TABLE myapp.user
DROP phone;
-- =========================================================
-- Table 常见 WITH 属性
-- =========================================================
CREATE TABLE myapp.data (
id uuid PRIMARY KEY,
value text
)
WITH comment = '业务数据表';
-- 压缩配置
CREATE TABLE myapp.data_compress (
id uuid PRIMARY KEY,
value text
)
WITH compression = {
'class': 'ZstdCompressor'
};
-- TTL 默认值
CREATE TABLE myapp.data_ttl (
id uuid PRIMARY KEY,
value text
)
WITH default_time_to_live = 86400;
-- GC Grace
CREATE TABLE myapp.data_gc (
id uuid PRIMARY KEY,
value text
)
WITH gc_grace_seconds = 864000;
-- Bloom Filter
CREATE TABLE myapp.data_bloom (
id uuid PRIMARY KEY,
value text
)
WITH bloom_filter_fp_chance = 0.01;
-- =========================================================
-- 修改 Table 属性
-- =========================================================
ALTER TABLE myapp.data
WITH comment = '修改后的描述';
ALTER TABLE myapp.data
WITH default_time_to_live = 86400;
ALTER TABLE myapp.data
WITH gc_grace_seconds = 864000;
Column
sql
-- =========================================================
-- 常见集合类型
-- =========================================================
CREATE TABLE myapp.collection_demo (
id uuid PRIMARY KEY,
-- List:有序、允许重复
tags list<text>,
-- Set:无序、不允许重复
roles set<text>,
-- Map:Key -> Value
attributes map<text, text>
);
-- =========================================================
-- Frozen
-- =========================================================
-- 将集合/UDT 作为一个整体存储
CREATE TABLE myapp.frozen_demo (
id uuid PRIMARY KEY,
address frozen<map<text, text>>
);
-- =========================================================
-- 静态列 Static Column
-- =========================================================
CREATE TABLE myapp.device_info (
device_id text,
create_time timestamp,
device_name text STATIC,
device_type text STATIC,
value double,
PRIMARY KEY (device_id, create_time)
);
-- =========================================================
-- PRIMARY KEY
-- =========================================================
-- Partition Key
CREATE TABLE myapp.user1 (
id text PRIMARY KEY,
name text
);
-- 等价于:
CREATE TABLE myapp.user2 (
id text,
name text,
PRIMARY KEY (id)
);
-- Partition Key + Clustering Column
CREATE TABLE myapp.user_log (
user_id text,
create_time timestamp,
message text,
PRIMARY KEY (user_id, create_time)
);
-- 多个 Partition Key
CREATE TABLE myapp.device_data2 (
device_id text,
device_type text,
create_time timestamp,
value double,
PRIMARY KEY ((device_id, device_type), create_time)
);
-- =========================================================
-- Column 添加 / 删除 / 修改
-- =========================================================
-- 添加列
ALTER TABLE myapp.user
ADD phone text;
-- 添加多个列
ALTER TABLE myapp.user
ADD address text;
ALTER TABLE myapp.user
ADD birthday date;
-- 修改列类型
ALTER TABLE myapp.user
ALTER age TYPE bigint;
-- 删除列
ALTER TABLE myapp.user
DROP phone;
索引
Cassandra之中的索引的实现相对MySQL的索引来说就要简单粗暴很多了。Cassandra自动新创建了一张表格,同时将原始表格之中的索引字段作为新索引表的Primary Key!并且存储的值为原始数据的Primary Key
sql
-- 创建索引
CREATE INDEX user_email_idx
ON myapp.user (email);
-- 删除索引
DROP INDEX myapp.user_email_idx;
物化视图
sql
-- =========================================================
-- Materialized View
-- =========================================================
-- 语法
create_materialized_view_statement::= CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] view_name AS select_statement PRIMARY KEY '(' primary_key')' WITH table_options
-- 创建物化视图
CREATE MATERIALIZED VIEW myapp.user_by_email AS
SELECT *
FROM myapp.user
WHERE email IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (email, id);
-- 删除物化视图
DROP MATERIALIZED VIEW myapp.user_by_email;
用户定义类型 UDT
sql
-- =========================================================
-- User Defined Type(UDT)
-- =========================================================
-- 创建 UDT
CREATE TYPE myapp.address (
province text,
city text,
detail text
);
-- 使用 UDT
CREATE TABLE myapp.customer (
id uuid PRIMARY KEY,
name text,
address frozen<address>
);
-- 删除 UDT
DROP TYPE myapp.address;
-- =========================================================
-- Table Options 常见属性总结
-- =========================================================
-- comment
-- Table 描述
-- default_time_to_live
-- 默认 TTL,单位:秒
-- 0 表示不设置默认 TTL
-- gc_grace_seconds
-- Tombstone 保留时间
-- 与 Repair / Compaction 密切相关
-- compression
-- SSTable 压缩方式
--
-- Cassandra 5.x 常见:
-- ZstdCompressor
-- LZ4Compressor
--
-- bloom_filter_fp_chance
-- Bloom Filter 的误判概率
-- caching
-- Cache 配置
-- compaction
-- Compaction 策略
-- memtable
-- MemTable 相关配置
数据查询
查询限制
因为是集群, 列式 存储的原理, 所有它有一些特殊的限制
Cassandra 查询使用索引的核心要求是:Partition Key 仅支持等值查询,Clustering Key 支持范围查询但必须连续且依赖前置键,二级索引仅支持等值查询且需全节点扫描,非索引列过滤必须显式加 ALLOW FILTERING。
| 列类型 | 支持的操作符 |
|---|---|
| Partition Key(第一主键) | 仅 = 和 IN |
| Clustering Key(第二主键) | = > < >= <= |
| 索引列(二级索引) | 仅 = |
| 普通列(无索引) | 需配合 ALLOW FILTERING |
主键(Primary Key)查询规则
Cassandra 的查询必须严格遵循主键的设计顺序,主键由 分区键(Partition Key)和聚簇键(Clustering Key)**组成。
- 分区键规则:查询条件必须包含完整的分区键(如果是复合分区键,则必须全部提供)。这决定了 Cassandra 能直接定位到数据所在的节点。
- 聚簇键连续性规则 :聚簇键的过滤条件不能跳跃 。例如主键为
(A, B, C, D),如果要在WHERE中使用C,则必须同时提供B的条件。 - 范围查询位置规则 :范围查询(
>,<,>=,<=)只能出现在聚簇键条件链的最后一个位置。一旦使用了范围查询,其后的聚簇键就不能再作为过滤条件。 - 操作符限制 :分区键通常只支持等值查询(
=)和IN查询;聚簇键支持等值查询和范围查询。
**索引与非主键列查询规则 - 二级索引(Secondary Index)限制 :可以通过二级索引对非主键列进行等值查询(
=),但不支持 对二级索引列进行范围查询(>,<)。 - ALLOW FILTERING 机制 :如果查询条件没有包含完整的分区键,或者对非索引的非主键列进行了过滤,Cassandra 会拒绝执行。必须显式加上
ALLOW FILTERING才能强制执行。- 性能警告 :
ALLOW FILTERING会导致 Cassandra 扫描整个集群或分区内的数据,查询时间不再与结果集大小成正比,而是与总数据量成正比。在生产环境中应极力避免。
- 性能警告 :
- LIKE 查询限制 :普通的二级索引不支持
LIKE模糊查询。如果必须使用,需要创建 SASI(SSTable Attached Secondary Index)自定义索引。
关于分组统计
在 Cassandra 中按列分组统计性能极差,属于典型的反模式;原因在于数据按 Partition Key 分散存储且不支持跨分区聚合。
但是: 如果是指定 Partition Key + 单列聚合",单节点顺序扫描,性能是极好的
sql
CREATE TABLE orders (
user_id INT,
created_at TIMESTAMP,
amount DECIMAL,
PRIMARY KEY (user_id, created_at)
);
其中 user_id 是 Partition Key,created_at 是 Clustering Key。
sql
SELECT SUM(amount) FROM orders
WHERE user_id = 1001
AND created_at >= '2026-08-01'
AND created_at < '2026-08-27';
整个过程:单节点 + 分区内顺序扫描 + 无跨分区通信。
Cassandra 的数据包括在内存中的和磁盘中的数据
这些数据主要分为三种:
CommitLog:主要记录客户端提交过来的数据以及操作。这种数据被持久化到磁盘中,方便数据没有被持久化到磁盘时可以用来恢复。
Memtable:用户写的数据在内存中的形式。
SSTable:数据被持久化到磁盘,又分为 Data、Index 和 Filter 三种数据格式。
数据存储的过程
https://cassandra.apache.ac.cn/doc/5.0/cassandra/architecture/storage-engine.html
写入 = 先顺序写 CommitLog 保证可靠性,再写 Memtable 保证速度;Memtable 满了刷成只读 SSTable;后台 Compaction
负责真正删除数据、合并文件、并生成 MerkleTree 供节点间数据修复。整个链路没有任何随机磁盘写,这是 Cassandra
高写入性能的根源。
1.CommitLog
- 写数据之前先记日志,确保任何情况下宕机都不丢数据。
- 数据按固定格式组成 byte 数组,先写入 IO 缓冲区,定时刷盘持久化。
- CommitLog 是 server 级别的,不区分表。
- 每个文件大小固定,称为一个 CommitlogSegment;写满即新建文件,旧文件不再需要时自动清除。
2.Memtable
- 写入的第二阶段,是一种内存结构,保存用户写入数据的内存形态。
- 每个 ColumnFamily(每张表)对应一个 Memtable。
- 数据量达到块大小时,批量 flush 到磁盘生成 SSTable。
- 优势:将随机 IO 写变成顺序 IO 写,降低大量写操作对存储系统的压力。
3.SSTable
- SSTable 是只读的,一个 Table 通常对应多个 SSTable。
- 读取时使用 Bloom Filter:通过多个 hash 函数把 key 映射到位图,快速判断 key 属于哪个 SSTable,避免逐个文件扫描。
SSTable 的本质
SSTable(Sorted String Table)是 Memtable flush 到磁盘后形成的 不可变(Immutable)**文件,数据按 partition key
排序存储。"只读"意味着:
- 写入完成后永不修改,更新和删除都不改动旧文件,而是在新的 SSTable 中写入新版本数据或墓碑(Tombstone)标记;
- 读取一条数据时,同一个 key 可能分散在多个 SSTable 中,需要按时间戳合并出最新版本(读放大的来源);
- 不可变带来的好处:无需加锁、顺序写盘、易于缓存和压缩、崩溃后文件不会处于半写状态。
SSTable 的文件组成
一个 SSTable 在磁盘上不是单个文件,而是一组同前缀的文件:
| 文件 | 作用 |
|---|---|
| Data.db | 真正的数据文件,按 key 排序存储各行数据 |
| Index.db | 分区索引,记录 partition key 到 Data.db 中偏移量的映射 |
| Filter.db | Bloom Filter 的持久化文件,判断 key 是否"可能存在"于本 SSTable |
| Summary.db | 索引的抽样(索引的索引),常驻内存,加速在 Index.db 中的定位 |
| Statistics.db | 统计信息:时间戳范围、墓碑数量、行数等,供 compaction 和读优化使用 |
| CompressionInfo.db | 压缩块的偏移量元数据(数据文件默认分块压缩) |
| TOC.txt | 记录本 SSTable 包含哪些组件文件 |
| Digest.crc32 | 数据文件的校验和,用于检测损坏 |
读取一个 key 时 SSTable 内部的查找路径
- Bloom Filter 只有假阳性、没有假阴性:说"不存在"就一定不存在,可安全跳过;说"存在"则可能误判,还需继续查索引确认。
- 一次读请求要对所有可能包含该 key 的 SSTable 走一遍上述流程,再与 Memtable 中的数据按时间戳合并,取最新值返回。
- SSTable 数量越多读放大越严重,这正是需要 compaction 的原因。
数据删除的过程
https://cassandra.apache.ac.cn/doc/5.0/cassandra/managing/operating/compaction/tombstones.html
Cassandra 将删除视为插入,并插入一个带时间戳的删除标记,称为墓碑。墓碑会经过 Cassandra 的写入路径,并写入一个或多个节点上的 SSTable。墓碑的主要特征区别在于它具有内置的过期日期/时间。在过期时间段(宽限期)结束时,墓碑将在 Cassandra 的正常压缩过程中被删除。
墓碑必须保留一段时间(gc_grace_seconds,默认 10 天)才能在 compaction中真正清除,目的是留出时间让宕机恢复的副本节点同步到这个删除,否则已删数据会"复活"。
在多节点集群中,Cassandra 可能会在两个或多个节点上存储相同数据的副本。这有助于防止数据丢失,但也使删除过程变得复杂。如果一个节点收到对其本地存储数据的删除命令,该节点会将指定对象标记为墓碑,并尝试将墓碑传递给包含该对象副本的其他节点。但如果其中一个副本节点此时无响应,它不会立即收到墓碑,因此它仍然包含该对象的删除前版本。如果在该节点恢复之前,墓碑对象已从集群的其余部分删除,Cassandra 会将恢复节点上的对象视为新数据,并将其传播到集群的其余部分。这种已删除但仍然存在的对象称为 僵尸。
为了防止僵尸重新出现,Cassandra 为每个墓碑设置了一个宽限期。墓碑的宽限期由表属性 WITH gc_grace_seconds 设置。其默认值为 864000 秒(十天),之后墓碑会过期,并在压缩期间被删除。在宽限期结束之前,Cassandra 会通过压缩事件保留墓碑。每个表都可以为此属性设置自己的值。
Compaction 策略
| 策略 | 适用场景 | 思路 |
|---|---|---|
| STCS(Size Tiered) | 写多读少,默认策略 | 把大小相近的 SSTable 分组合并,写放大小但空间放大和读放大较大 |
| LCS(Leveled) | 读多写少、读延迟敏感 | 按层级组织,每层内 SSTable 的 key 范围互不重叠,一个 key 每层最多查一个文件,读放大小但写放大大 |
| TWCS(Time Window) | 时序数据、带 TTL 的数据 | 按时间窗口分组合并,整窗过期后可直接整文件删除 |
Cassandra 集群环与一致性哈希
一致性哈希
https://cassandra.apache.ac.cn/doc/5.0/cassandra/architecture/dynamo.html
Cassandra 使用一种称为 一致性哈希 的特殊形式的哈希,将数据分区到存储节点上。在简单的哈希中,通常通过对键进行哈希并对桶的数量取模来将键分配到桶中。例如,如果您想使用简单的哈希将数据分布到 100 个节点,您可能会将每个节点分配到 0 到 100 之间的桶中,对输入键进行哈希并对 100 取模,并将数据存储在关联的桶中。但是,在这种简单的方案中,添加单个节点可能会使几乎所有映射失效。
Cassandra 而是将每个节点映射到令牌环上的一个或多个令牌,并通过对键进行哈希并沿着环"行走"来定义所有权,类似于 Chord 算法。一致性哈希与简单哈希的主要区别在于,当要哈希到的节点(桶)数量发生变化时,一致性哈希只需要移动一小部分键。
例如,如果我们有一个八节点集群,令牌均匀分布,复制因子 (RF) 为 3,那么要找到某个键的所有者节点,我们首先对该键进行哈希以生成令牌(这只是键的哈希值),然后我们沿着环顺时针"行走",直到遇到三个不同的节点,此时我们找到了该键的所有副本。这个八节点集群的示例,gRF=3,可以可视化为如下
您可以看到,在类似 Dynamo 的系统中,键的范围,也称为 令牌范围,映射到相同的物理节点集。在这个示例中,所有落在令牌范围内的键,不包括令牌 1,包括令牌 2 (grange(t1, t2]),都存储在节点 2、3 和 4 上。
为什么需要一致性哈希?
普通取模哈希(hash(key) % N)的问题:节点数 N 一变,几乎所有 key 的归属都要重算,扩缩容时会引发全量数据迁移。一致性哈希把哈希空间组织成一个首尾相接的环,节点增减只影响环上相邻的一小段数据,迁移量从"全部"降为"约 1/N"。
Token 环
- 整个哈希值空间构成一个环 。Cassandra 默认使用 Murmur3Partitioner ,哈希范围为
-2^63 ~ 2^63-1。 - 每个节点被分配一个(或多个)Token(可以理解为子环上的一个点),即它在环上的位置。
- Token Range 两点之间的一段弧 (上一个token, 本token],这才是节点实际"负责的范围"
- 写入数据时,对 partition key 计算哈希得到 token,沿顺时针方向找到第一个 token 大于等于它的节点,该节点即为这条数据的第一副本节点(协调其余副本)。
- 每个节点负责的范围:从上一个节点的 token(不含)到自己的 token(含) 这一段弧。
**in short : 环 = 表盘;token = 表盘上的刻度;Token Range = 相邻刻度之间的扇区
假设 4 个节点均分环(简化为 0~100 的哈希空间):
| 节点 | Token | 负责范围 |
|---|---|---|
| A | 25 | (0, 25] |
| B | 50 | (25, 50] |
| C | 75 | (50, 75] |
| D | 100 | (75, 100] 及 (100, 0] 回绕段 |
某 key 哈希后 token = 62,落在 (50, 75],由节点 C 负责;若副本因子为 3,则副本继续放在顺时针的 D 和 A 上。
节点增减时的影响
flowchart LR subgraph before加入前 B1B: 负责 25\~50 end subgraph after新节点 E 加入 token=40 E1E: 接管 25\~40 B2B: 只剩 40\~50 end before --> after
- 新增节点:只从顺时针方向的下一个节点分走一段范围,其他节点数据不动。
- 移除节点:其负责范围并入顺时针的下一个节点。
虚拟节点(Vnodes)
只给每个物理节点一个 token 有两个问题:数据分布不均匀;新节点加入时只有一个邻居为它传数据,压力集中。
Cassandra 的解决方案是虚拟节点 :每个物理节点在环上持有多个 token(num_tokens,新版本默认 16,早期默认 256),即把环切成大量小段,随机散布到各物理节点。
| 对比项 | 单 Token | Vnodes |
|---|---|---|
| 数据均匀性 | 依赖人工精心分配 token | 大量随机小段,天然趋于均匀 |
| 扩容数据来源 | 仅相邻一个节点 | 从集群中多个节点并行流入,更快 |
| 节点故障恢复 | 由少数邻居承担重建 | 重建压力分摊到全集群 |
| 异构机器 | 难以支持 | 性能强的机器可配更多 vnode 多担数据 |
副本放置策略
token 只决定第一副本的位置,其余副本由副本策略决定:
| 策略 | 放置规则 | 适用 |
|---|---|---|
| SimpleStrategy | 从第一副本开始,沿环顺时针依次放在后续节点上,不感知机架和数据中心 | 单数据中心、测试环境 |
| NetworkTopologyStrategy | 可为每个数据中心单独指定副本数;同一数据中心内尽量将副本放在不同机架上 | 生产环境标准选择 |
Cassandra 用一致性哈希把数据按 partition key 的 token 映射到首尾相接的环上,节点各自认领环上的区段;vnodes 让分布更均匀、扩缩容更平滑;副本沿环顺时针并结合机架/数据中心拓扑放置。任何节点都能据此直接算出数据位置,无需中心节点,这是其线性扩展能力的核心。
有用的资料
生成环境建议 - https://cassandra.apache.ac.cn/doc/5.0/cassandra/getting-started/production.html#tokens
Nodetool工具 - https://cassandra.apache.ac.cn/doc/5.0/cassandra/managing/tools/nodetool/nodetool.html