数据分布与分区 #
一、数据分布概述 #
1.1 分布式数据存储 #
Cassandra使用一致性哈希算法将数据分布到集群中的各个节点。
text
数据分布原理:
┌─────────────────────────────────────────────────────────┐
│ Token Ring (令牌环) │
│ │
│ Node A │
│ (Token: 0) │
│ / \ │
│ / \ │
│ Node D / \ Node B │
│ (Token: 75)│ │(Token: 25) │
│ │ │ │
│ \ / │
│ \ / │
│ Node C │
│ (Token: 50) │
│ │
│ 数据根据分区键哈希值映射到Token范围 │
└─────────────────────────────────────────────────────────┘
1.2 分区过程 #
text
分区流程:
用户数据
│
▼
┌─────────────────────────────────────────────────────────┐
│ 1. 计算分区键的哈希值 │
│ hash(partition_key) → Token │
│ │
│ 2. 根据Token确定数据所属节点 │
│ Token → Node │
│ │
│ 3. 根据复制策略复制到其他节点 │
│ Node → Replica Nodes │
└─────────────────────────────────────────────────────────┘
│
▼
数据存储到对应节点
二、分区键 (Partition Key) #
2.1 分区键定义 #
分区键决定数据存储在哪个节点,是主键的第一部分。
sql
-- 单列分区键
CREATE TABLE users (
user_id UUID, -- 分区键
name TEXT,
email TEXT,
PRIMARY KEY (user_id)
);
-- 复合分区键
CREATE TABLE user_events (
user_id UUID, -- 分区键部分1
event_date DATE, -- 分区键部分2
event_id UUID, -- 聚簇列
event_data TEXT,
PRIMARY KEY ((user_id, event_date), event_id)
);
2.2 分区键选择原则 #
text
分区键选择原则:
1. 高基数
✓ user_id, device_id, order_id
✗ gender, status, country
2. 数据均匀分布
✓ 哈希分布均匀的列
✗ 时间序列(可能导致热点)
3. 查询模式匹配
✓ 经常作为查询条件的列
✗ 很少用于查询的列
4. 避免热点
✓ 组合分区键分散写入
✗ 单一时间序列分区键
2.3 分区键示例 #
sql
-- 不好的设计:可能导致热点
CREATE TABLE events_bad (
event_date DATE,
event_id UUID,
data TEXT,
PRIMARY KEY (event_date, event_id)
);
-- 问题:同一天的所有事件写入同一分区
-- 好的设计:分散写入
CREATE TABLE events_good (
event_date DATE,
bucket INT, -- 时间桶,分散数据
event_id UUID,
data TEXT,
PRIMARY KEY ((event_date, bucket), event_id)
);
-- 优势:同一天的数据分散到多个分区
三、Token环 #
3.1 Token范围 #
Cassandra使用Murmur3Partitioner作为默认分区器,Token范围是-2^63到2^63-1。
text
Token环结构:
-2^63 (最小Token)
│
│
┌────────────┼────────────┐
│ │ │
Node D Node A Node B
(Token: -4611686018427387904)
│ │ │
│ │ │
└────────────┼────────────┘
│
│
2^63-1 (最大Token)
每个节点负责一段Token范围:
Node A: -4611686018427387904 到 0
Node B: 0 到 4611686018427387904
Node D: -9223372036854775808 到 -4611686018427387904
3.2 Token计算 #
sql
-- 查看数据的Token值
SELECT token(user_id), user_id, name FROM users;
-- 使用token函数查询特定范围
SELECT * FROM users
WHERE token(user_id) > token(550e8400-e29b-41d4-a716-446655440000);
-- 分页查询
SELECT * FROM users
WHERE token(user_id) > token(上一页最后的user_id)
LIMIT 100;
3.3 虚拟节点 (vNode) #
text
虚拟节点将每个物理节点划分为多个Token范围:
物理节点
├── vNode 1 (Token: 0-100)
├── vNode 2 (Token: 500-600)
├── vNode 3 (Token: 900-1000)
└── ...
优势:
├── 更均匀的数据分布
├── 更简单的集群管理
├── 更快的负载均衡
└── 更容易的容量规划
3.4 vNode配置 #
yaml
# cassandra.yaml
# 虚拟节点数量
num_tokens: 256
# 初始Token(不使用vNode时需要指定)
# initial_token:
# 分区器
partitioner: org.apache.cassandra.dht.Murmur3Partitioner
四、分区器 #
4.1 分区器类型 #
| 分区器 | 描述 | Token范围 | 推荐场景 |
|---|---|---|---|
| Murmur3Partitioner | 默认分区器 | -2^63 到 2^63-1 | 大多数场景 |
| RandomPartitioner | 随机分区 | 0 到 2^127-1 | 兼容旧版本 |
| ByteOrderedPartitioner | 有序分区 | 字节有序 | 特殊排序需求 |
4.2 Murmur3Partitioner #
java
// Murmur3哈希算法
public long hash(byte[] key) {
// 使用Murmur3算法计算64位哈希值
return Murmur3.hash64(key);
}
// 特点:
// 1. 计算速度快
// 2. 分布均匀
// 3. 碰撞率低
4.3 分区器选择 #
yaml
# cassandra.yaml
# 推荐:Murmur3Partitioner
partitioner: org.apache.cassandra.dht.Murmur3Partitioner
# 注意:分区器一旦设置,不可更改!
五、数据分片 #
5.1 分区大小 #
text
分区大小建议:
┌─────────────────────────────────────────────────────────┐
│ 分区大小限制 │
├──────────────────┬──────────────────────────────────────┤
│ 推荐大小 │ < 100MB │
├──────────────────┼──────────────────────────────────────┤
│ 警告阈值 │ > 100MB │
├──────────────────┼──────────────────────────────────────┤
│ 最大限制 │ 2GB │
└──────────────────┴──────────────────────────────────────┘
大分区问题:
├── 读取性能下降
├── 修复时间增加
├── 压缩效率降低
└── 内存压力增大
5.2 分区设计示例 #
sql
-- 问题设计:无限增长的分区
CREATE TABLE user_posts_bad (
user_id UUID,
post_id TIMEUUID,
content TEXT,
PRIMARY KEY (user_id, post_id)
) WITH CLUSTERING ORDER BY (post_id DESC);
-- 问题:活跃用户的分区会无限增长
-- 优化设计:按时间桶分区
CREATE TABLE user_posts_good (
user_id UUID,
post_month TEXT, -- 格式:YYYY-MM
post_id TIMEUUID,
content TEXT,
PRIMARY KEY ((user_id, post_month), post_id)
) WITH CLUSTERING ORDER BY (post_id DESC);
-- 优势:每个用户每月一个分区,大小可控
-- 查询示例
SELECT * FROM user_posts_good
WHERE user_id = ? AND post_month = '2024-01';
5.3 分区统计 #
bash
# 查看分区大小
nodetool tablestats my_keyspace.my_table
# 查看分区分布
nodetool ring
# 查看特定节点的数据分布
nodetool netstats
六、聚簇列 (Clustering Column) #
6.1 聚簇列定义 #
聚簇列决定分区内的数据排序。
sql
-- 聚簇列示例
CREATE TABLE orders (
user_id UUID, -- 分区键
order_date DATE, -- 聚簇列1
order_id UUID, -- 聚簇列2
amount DECIMAL,
status TEXT,
PRIMARY KEY (user_id, order_date, order_id)
) WITH CLUSTERING ORDER BY (order_date DESC, order_id ASC);
-- 分区内数据排序:
-- user1 | 2024-01-15 | order3 (最新日期在前)
-- user1 | 2024-01-15 | order1
-- user1 | 2024-01-10 | order2
-- user1 | 2024-01-10 | order1
6.2 排序规则 #
sql
-- 默认升序
CREATE TABLE example_asc (
pk INT,
ck INT,
data TEXT,
PRIMARY KEY (pk, ck)
); -- ck升序排列
-- 指定降序
CREATE TABLE example_desc (
pk INT,
ck INT,
data TEXT,
PRIMARY KEY (pk, ck)
) WITH CLUSTERING ORDER BY (ck DESC); -- ck降序排列
-- 多列排序
CREATE TABLE example_multi (
pk INT,
ck1 INT,
ck2 INT,
data TEXT,
PRIMARY KEY (pk, ck1, ck2)
) WITH CLUSTERING ORDER BY (ck1 DESC, ck2 ASC);
6.3 聚簇列查询 #
sql
-- 必须按聚簇列顺序查询
SELECT * FROM orders
WHERE user_id = ?
AND order_date = ?; -- 可以
SELECT * FROM orders
WHERE user_id = ?
AND order_date = ?
AND order_id = ?; -- 可以
SELECT * FROM orders
WHERE user_id = ?
AND order_id = ?; -- 不可以!跳过了order_date
-- 范围查询
SELECT * FROM orders
WHERE user_id = ?
AND order_date >= '2024-01-01'
AND order_date <= '2024-01-31';
-- 排序(仅支持聚簇列)
SELECT * FROM orders
WHERE user_id = ?
ORDER BY order_date DESC; -- 可以
SELECT * FROM orders
WHERE user_id = ?
ORDER BY amount DESC; -- 不可以!amount不是聚簇列
七、主键设计模式 #
7.1 设计模式对比 #
text
模式1:单分区键
┌─────────────────────────────────────────────────────────┐
│ PRIMARY KEY (partition_key) │
│ │
│ 适合:主键查询场景 │
│ 例如:用户信息、配置数据 │
└─────────────────────────────────────────────────────────┘
模式2:分区键 + 聚簇列
┌─────────────────────────────────────────────────────────┐
│ PRIMARY KEY (partition_key, clustering_column) │
│ │
│ 适合:一对多关系、时间序列 │
│ 例如:用户订单、事件日志 │
└─────────────────────────────────────────────────────────┘
模式3:复合分区键 + 聚簇列
┌─────────────────────────────────────────────────────────┐
│ PRIMARY KEY ((pk1, pk2), clustering_column) │
│ │
│ 适合:需要分散数据、避免热点 │
│ 例如:时间序列数据、大量关联数据 │
└─────────────────────────────────────────────────────────┘
7.2 设计示例 #
sql
-- 场景1:用户信息
CREATE TABLE users (
user_id UUID PRIMARY KEY,
name TEXT,
email TEXT
);
-- 场景2:用户订单
CREATE TABLE user_orders (
user_id UUID,
order_id UUID,
order_date TIMESTAMP,
amount DECIMAL,
PRIMARY KEY (user_id, order_id)
) WITH CLUSTERING ORDER BY (order_id DESC);
-- 场景3:时间序列数据(避免热点)
CREATE TABLE sensor_data (
sensor_id TEXT,
date DATE,
hour INT,
reading_time TIMESTAMP,
value DOUBLE,
PRIMARY KEY ((sensor_id, date, hour), reading_time)
) WITH CLUSTERING ORDER BY (reading_time ASC);
-- 场景4:多租户应用
CREATE TABLE tenant_data (
tenant_id UUID,
user_id UUID,
created_at TIMESTAMP,
data TEXT,
PRIMARY KEY ((tenant_id, user_id), created_at)
) WITH CLUSTERING ORDER BY (created_at DESC);
八、数据分布监控 #
8.1 监控命令 #
bash
# 查看集群状态
nodetool status
# 查看数据分布
nodetool ring
# 查看表统计
nodetool tablestats
# 查看特定表的数据分布
nodetool cfstats my_keyspace.my_table
# 查看节点负载
nodetool netstats
8.2 数据平衡 #
bash
# 检查数据平衡
nodetool ring | grep -A 1 "Token" | sort
# 手动触发负载均衡
nodetool cleanup
nodetool repair
# 查看数据迁移进度
nodetool streaminfo
九、总结 #
数据分布要点:
- 分区键选择:高基数、均匀分布、匹配查询模式
- Token环:一致性哈希实现数据分布
- 虚拟节点:简化集群管理,均匀分布数据
- 分区大小:控制在100MB以内
- 聚簇列:分区内排序,支持范围查询
- 主键设计:基于查询模式设计
下一步,让我们学习复制策略!
最后更新:2026-03-27