Redis分片集群散列插槽

一、前言:为什么 Redis Cluster 用 16384 个槽?

在 Redis 分片集群(Cluster)中,数据不是随机分布,而是通过"散列插槽"(Hash Slot)机制进行分片

你可能好奇:

  • 为什么是 16384 个槽,不是 65536 或 1024?
  • key 是如何映射到具体节点的?
  • 为什么 MGET user:1 user:2 有时会报错?

本文将带你彻底搞懂 Redis 散列插槽的设计哲学与工作原理


二、散列插槽:Redis Cluster 的分片基石

2.1 基本概念

  • Redis Cluster 将整个 key 空间划分为 16384 个哈希槽(slot)

  • 每个 key 通过公式计算归属哪个 slot:

    复制代码
    slot = CRC16(key) % 16384
  • 每个 master 节点负责一部分连续或不连续的 slot

优势

  • 分片规则简单、确定
  • 扩容/缩容只需迁移 slot,无需 rehash 全量数据

三、为什么是 16384?不是更多或更少?

这是 Redis 作者 Salvatore Sanfilippo(antirez) 在 GitHub issue 中亲自解释的经典问题。

3.1 官方理由(精简版)

"16384 is the right balance between:

  • 消息大小:每个节点每秒通过 gossip 协议广播 cluster state
  • 集群规模:最多支持 1000 个节点(实际建议 ≤ 100)
  • 内存开销:每个 slot 状态需 2 字节,16384 × 2 = 32KB"

3.2 详细分析

槽位数 每节点心跳包大小 支持最大节点数 内存开销
65536 ~130KB < 100 128KB
16384 ~32KB ~1000 32KB
4096 ~8KB > 1000 8KB
  • 65536 太大:心跳包过大,网络带宽浪费
  • 4096 太小:节点多时,slot 分配不均(如 100 节点,平均仅 40 个 slot/节点)
  • 16384 刚好:兼顾扩展性与效率

🔑 结论 :16384 是工程上的最优折中


四、Key 到 Slot 的计算过程(含代码)

4.1 CRC16 算法

Redis 使用 CRC16_CCITT 变种(初始值 0,无反转)。

4.2 Java 实现示例

java 复制代码
public class RedisSlot {
    private static final int SLOT_COUNT = 16384;

    public static int getSlot(String key) {
        // 1. 提取 Hash Tag(如有)
        String tagKey = extractTag(key);
        // 2. 计算 CRC16
        int crc = crc16(tagKey.getBytes(StandardCharsets.UTF_8));
        // 3. 取模
        return crc % SLOT_COUNT;
    }

    // 支持 Hash Tag:{user1001}.name 和 {user1001}.age 落在同一 slot
    private static String extractTag(String key) {
        int start = key.indexOf('{');
        if (start != -1) {
            int end = key.indexOf('}', start + 1);
            if (end != -1 && end != start + 1) {
                return key.substring(start + 1, end);
            }
        }
        return key;
    }

    // 简化版 CRC16(实际应使用标准实现)
    private static int crc16(byte[] bytes) {
        int crc = 0;
        for (byte b : bytes) {
            crc = ((crc << 8) ^ LOOKUP_TABLE[((crc >> 8) ^ (b & 0xFF)) & 0xFF]) & 0xFFFF;
        }
        return crc;
    }

    private static final int[] LOOKUP_TABLE = { /* CRC16 表 */ };
}

关键点Hash Tag 可强制多个 key 落在同一 slot!


五、客户端如何定位 Key 所在节点?

当客户端访问一个 key 时,流程如下:

5.1 首次访问(无本地缓存)

  1. 随机连接一个节点(如 7001)

  2. 节点计算 key 的 slot,发现不在自己负责范围

  3. 返回 MOVED 重定向

    复制代码
    MOVED 5461 192.168.1.10:7002
  4. 客户端缓存 <slot -> node> 映射,并重连 7002

5.2 后续访问(有缓存)

  • 直接根据 slot 路由到目标节点,无重定向

💡 Lettuce / Jedis Cluster 客户端会自动处理 MOVED/ASK


六、跨 Slot 操作为何失败?

6.1 问题场景

bash 复制代码
# 假设 user:1 → slot 1000, user:2 → slot 2000
redis-cli -c MGET user:1 user:2
# 报错:CROSSSLOT Keys in request don't hash to the same slot

6.2 原因

  • Redis Cluster 要求多 key 操作必须在同一 slot
  • 否则无法保证原子性和一致性

6.3 解决方案:使用 Hash Tag

bash 复制代码
# 强制 user:1 和 user:2 落在同一 slot
MGET {user100}.name {user100}.age  # OK!
  • {} 内的内容作为 hash key
  • user100 相同 → slot 相同

最佳实践:对需要 multi-key 操作的数据,使用相同 Hash Tag


七、Slot 迁移与集群扩容

当新增 master 节点时,需迁移部分 slot

7.1 迁移流程

  1. 目标节点声明"我将接管 slot X"
  2. 源节点进入 MIGRATING 状态
  3. 客户端访问 slot X 的 key:
    • 若 key 存在 → 返回 ASK 重定向 到目标节点
    • 若 key 不存在 → 允许在目标节点写入
  4. 源节点逐步迁移 key(CLUSTER GETKEYSINSLOT + MIGRATE
  5. 迁移完成,更新集群拓扑

7.2 客户端感知

  • 收到 ASK 时,先发 ASKING 命令,再发原命令
  • Lettuce/Jedis 自动处理,业务无感

八、监控与运维命令

8.1 查看 slot 分配

bash 复制代码
redis-cli -p 7001 CLUSTER SLOTS
# 返回:[[0,5460,"192.168.1.10",7001], [5461,10922,"192.168.1.10",7002], ...]

8.2 查看 key 所属 slot

bash 复制代码
redis-cli -p 7001 CLUSTER KEYSLOT "user:1001"
# 返回:5461

8.3 查看节点负责的 slots

bash 复制代码
redis-cli -p 7001 CLUSTER NODES
# 输出中:connected 0-5460

九、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
2501_911088231 小时前
Web开发与API
jvm·数据库·python
2501_911088231 小时前
使用Python自动收发邮件
jvm·数据库·python
极客on之路1 小时前
分库分表(四)
数据库
zklgin2 小时前
【MySQL】深度学习数据库开发技术:使用CC++语言访问数据库
数据库·mysql·数据库开发
2401_889884662 小时前
使用Pandas进行数据分析:从数据清洗到可视化
jvm·数据库·python
李宥小哥2 小时前
SQLite04-表数据管理
java·jvm·数据库
Smoothcloud_润云2 小时前
GORM 事务管理与 Repository 模式完整指南
前端·数据库·代码规范
aq55356002 小时前
SQL 注入漏洞原理以及修复方法
网络·数据库·sql
七夜zippoe2 小时前
PostgreSQL高级特性在Python中的实战:JSONB、全文搜索、物化视图与分区表深度解析
数据库·python·postgresql·性能优化·分区表