数据分布与分区 #

一、数据分布概述 #

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

九、总结 #

数据分布要点:

  1. 分区键选择:高基数、均匀分布、匹配查询模式
  2. Token环:一致性哈希实现数据分布
  3. 虚拟节点:简化集群管理,均匀分布数据
  4. 分区大小:控制在100MB以内
  5. 聚簇列:分区内排序,支持范围查询
  6. 主键设计:基于查询模式设计

下一步,让我们学习复制策略!

最后更新:2026-03-27