Redis 分片集群与哈希槽笔记

一、什么是 Redis 分片集群

Redis 分片集群通常指 Redis Cluster。

它的主要作用是:

  1. 将数据分散到多个 Redis 主节点上。

  2. 突破单台 Redis 的内存容量限制。

  3. 提高整个集群的读写能力。

  4. 配合主从复制实现高可用。

典型结构:

复制代码
master A        master B        master C
   |               |               |
replica A       replica B       replica C

其中:

  • 多个 master 保存不同的数据,负责数据分片。

  • replica 复制对应 master 的数据,负责故障恢复。

因此:

复制代码
Redis Cluster = 数据分片 + 主从复制 + 故障转移

二、什么是哈希槽

Redis Cluster 将整个数据空间划分为:

复制代码
16384 个哈希槽

槽编号范围:

复制代码
0 ~ 16383

槽不是实际的内存块,也不会提前占用固定大小的内存。

槽本质上只是一个逻辑编号,用来对 key 进行分组和路由。

例如:

复制代码
槽 100:可能没有 key
槽 200:可能有 1 个 key
槽 300:可能有 10000 个 key

因此,槽的数据量并不固定。


三、key 如何确定所属槽

Redis 会根据 key 计算槽号:

复制代码
slot = CRC16(key) % 16384

例如:

复制代码
user:1  → 槽 3000
shop:1  → 槽 7000
order:1 → 槽 13000

只要 key 的内容不变,按照同样的计算规则,它对应的槽号就不会改变。

完整关系:

复制代码
key
 ↓ 计算
槽号
 ↓ 查询归属
Redis master 节点

例如:

复制代码
user:1
  ↓
槽 3000
  ↓
master A

所以数据不是直接根据 key 分配给节点,而是经过一层槽:

复制代码
key → 槽 → 节点

四、槽和数据的关系

槽是对数据的逻辑分组,不是实际保存数据的地方。

真正的数据仍然保存在 Redis 节点自己的内存中。

例如:

复制代码
槽 0~5000       → master A
槽 5001~10000   → master B
槽 10001~16383  → master C

如果:

复制代码
user:1 → 槽 3000

那么 user:1 就实际保存在 master A 的内存中。

可以理解为:

复制代码
槽属于哪个 master
        ↓
该槽中的 key 就存在哪个 master

五、为什么需要槽

如果不使用槽,而直接按照节点数量计算:

复制代码
节点 = hash(key) % 节点数量

原来有 3 个节点:

复制代码
hash(key) % 3

扩容为 4 个节点后:

复制代码
hash(key) % 4

因为取模数字发生变化,大量 key 的计算结果都会改变,因此可能需要迁移大量数据。

使用固定的 16384 个槽后:

复制代码
key → 槽

这个映射基本不变。

扩容或缩容时只需要修改:

复制代码
槽 → 节点

然后迁移这些槽中的数据。

因此,槽的核心作用是:

将 key 的分布规则与 Redis 节点数量分离。


六、扩容时槽如何迁移

假设原来:

复制代码
槽 5000 → master A

槽 5000 中的数据也都存放在 master A。

现在增加 master B,并准备将槽 5000 交给 B。

不能只修改槽的归属,而不迁移数据。

正确过程是:

复制代码
1. 将槽 5000 标记为正在从 A 迁移到 B
2. 将槽 5000 中的 key 从 A 逐个迁移到 B
3. 数据迁移完成
4. 正式将槽 5000 的归属修改为 B

最终结果:

复制代码
迁移前:

槽 5000 → master A
数据     → master A


迁移后:

槽 5000 → master B
数据     → master B

所以扩容时真正需要处理两件事:

复制代码
修改槽归属
+
迁移槽中的实际数据

七、迁移过程中数据在哪里

迁移槽不是直接搬运一整块内存,而是逐个迁移槽中的 key。

例如槽 5000 中有:

复制代码
key1
key2
key3

迁移过程中可能出现:

复制代码
key1 已经迁移到 B
key2 仍然留在 A
key3 仍然留在 A

因此,迁移过程中数据可能暂时同时分布在 A 和 B。

Redis 会通过迁移状态和 ASK 重定向保证请求能够找到正确的数据。

迁移完成后:

复制代码
槽 5000 中的所有 key 都在 B
槽 5000 正式归 B 管理

正常情况下不会长期存在:

复制代码
槽归 B 管理
数据却一直留在 A

因为 Redis 节点只能直接管理自己进程内存中的数据,B 不能直接管理 A 内存中的 key。


八、ASK 和 MOVED

1. ASK

槽正在迁移时,如果客户端访问了已经被迁移到新节点的 key,旧节点会返回:

复制代码
ASK 槽号 新节点地址

含义:

复制代码
该槽还在迁移,这一次请求暂时去新节点执行。

ASK 是临时重定向。


2. MOVED

槽已经正式归新节点管理后,客户端访问旧节点会收到:

复制代码
MOVED 槽号 新节点地址

含义:

复制代码
该槽已经正式属于新节点,以后都应该访问新节点。

MOVED 是正式重定向。


九、不同 key 存在不同服务器怎么办

这是分片集群的正常情况。

例如:

复制代码
user:1  → master A
shop:1  → master B
order:1 → master C

客户端会:

复制代码
1. 根据 key 计算槽号
2. 查询该槽由哪个节点负责
3. 将请求发送给正确节点

通常由 Lettuce、Jedis、Redisson 等集群客户端自动完成。

因此单 key 操作一般没有问题:

复制代码
GET user:1
SET shop:1 value
HGET order:1 status

客户端会自动将不同请求发送给不同节点。


十、多 key 操作的问题

如果一条命令同时操作多个 key,而这些 key 属于不同槽,就可能报错:

复制代码
CROSSSLOT Keys in request don't hash to the same slot

例如:

复制代码
MGET user:1 shop:1

假设:

复制代码
user:1 → master A
shop:1 → master B

一条 Redis 命令无法同时在两个节点上原子执行。

受影响的操作包括:

复制代码
MGET
MSET
DEL 多个 key
事务
Lua 脚本
集合交集、并集、差集

十一、Hash Tag

为了让相关 key 进入同一个槽,可以使用 {}

例如:

复制代码
user:{1}:name
user:{1}:age
user:{1}:orders

Redis 计算槽位时,只计算大括号中的内容:

复制代码
1

因此这些 key 一定属于同一个槽,也会存放在同一个 master 节点上。

例如:

复制代码
seckill:{10}:stock
seckill:{10}:order

这两个 key 都使用 {10} 计算槽号,因此可以在同一个 Lua 脚本中操作。

但是:

复制代码
user:{1}:name
user:{2}:name

大括号内容不同,仍然可能进入不同槽。


十二、核心理解

Redis Cluster 中有两层映射:

复制代码
第一层:key → 槽
第二层:槽 → master 节点

其中:

复制代码
key → 槽

由固定的哈希规则决定,只要 key 不变,槽号通常就不变。

而:

复制代码
槽 → 节点

可以随着扩容、缩容、故障转移而改变。

当槽的归属改变时,必须将槽中的实际数据迁移到新的节点。

最终可以记住这句话:

key 决定自己属于哪个槽,集群决定槽属于哪个 master;槽归属发生变化时,槽中的数据也必须迁移到新的 master。

相关推荐
遨游DATA1 小时前
Spring Boot 启动失败排查:如何区分多 Bean 冲突与清理阶段异常
java·spring boot·后端
我命由我123451 小时前
复利极简理解
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
wabs6662 小时前
关于单调栈【力扣739.每日温度的思考】
java·开发语言
AOwhisky2 小时前
Python 学习笔记(第三期)——流程控制核心知识点自测与详解
开发语言·笔记·python·学习·云原生·运维开发·流程控制
雪碧聊技术2 小时前
为什么要学函数式编程?
java·函数式编程
aaPIXa6222 小时前
C++ 用 13 条规则让模型写出安全现代 C++
java·c++·安全
我命由我123453 小时前
72 法则极简理解
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
Lihua奏3 小时前
Redis:它不只是缓存,还是高并发系统的工具箱
redis
2601_949817724 小时前
C++指针与引用深度精讲:底层原理、差异对比与高阶实战陷阱
java·jvm·c++