【Redis#16】Redis 集群

📃个人主页:island1314
⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
- 生活总是不会一帆风顺,前进的道路也不会永远一马平川,如何面对挫折影响人生走向 – 《人民日报》
🔥 目录
一、前言
上篇文章讲述的哨兵模式, 提高了系统的可用性. 但是真正用来存储数据的还是 master 和 slave 节点. 所有的数据都需要存储在单个 master 和 slave 节点中.
- 如果数据量很大, 接近超出了 master/slave 所在机器的物理内存, 就可能出现严重问题了.
- 虽然硬件价格在不断降低, 一些中大厂的服务器内存已经可以达到 TB 级别了, 但是 1TB 在当前这个 “大数据” 时代, 俨然不算什么, 有的时候我们确实需要更大的内存空间来保存更多的数据.
如何获取更大的空间?
- 加机器即可! 所谓 “大数据” 的核心, 其实就是一台机器搞不定了, 用多台机器来解决.
Redis 的集群就是在上述的思路之下, 引入多组 Master/Slave, 每一组 Master/Slave 存储数据全集的一部分, 从而构成一个更大的整体, 称为 Redis 集群 (Cluster)
二、集群
集群这个词,可以从广义上来理解,也可以从狭义上来理解。
- 其中 广义的集群,只要是多个机器,构成了分布式系统。我们前面学过的哨兵模式,也可以算作是一种广义上的集群。
- 狭义的集群,是 Redis 提供的一种集群模式,在这个集群模式之下,主要解决存储空间不足的问题,即 拓展存储空间。
我们前面的哨兵模式提高了系统的可用性,哨兵模式中,本质上还是 Redis 主从节点存储数据,其中要是请求一个主节点/从节点,就得存储整个数据的“全集”。
- 但是集群中主要要解决的问题就是要 引入多台机器, 每台机器存储一部分数据。
只要机器规模足够大, 就可以存储任意数据的大小了。比如:假定整个数据全集是1TB,引入三组 Master/Slave 来存储,那么每一组机器只需要存储整个数据全集的 1/3 即可

上面三组机器存储的数据都是不同的
- Redis 的集群通过引入多组 Master/Slave 来分散数据存储压力。
- 每一组 Master/Slave 存储数据全集的一部分,构成一个更大的整体。
- 每个 Slave 是对应 Master 的备份(Master 故障时,Slave 会补位成 Master)。
- 每个 红框部分称为一个分片 (Sharding),用于进一步扩展存储容量(如:全量数据进一步增加,只需增加更多分片即可)
关于使用硬盘代替内存
- 硬盘虽然可以存储更多数据,但访问速度远慢于内存。
- 在某些应用场景(如搜索引擎),既需要存储较多数据,又希望有非常高的读写速度。这时我们就可以引入 redis 集群啦
这其中每个服务器集群上都需要存储一定规模的数据,也就是把数据分为了多份,但是数据在分成多份的时候,应该怎么分?这就是我们接下来要研究的
三、数据分片算法
分片的核心思路是用多组机器来存数据的每个部分,那么接下来的核心问题就是,给定一个数据(一个具体的 key),那么这个数据应该存储在哪个分片上?读取的时候又应该去哪个分片读取? 围绕这个问题,业界有三种比较主流的实现方式:
- 哈希求余
- 一致性哈希算法
- 哈希槽分区算法
| 方法 | 描述 | 特点 |
|---|---|---|
| 普通哈希求余 | hash(key) % N,N 是节点数 |
扩容时大量 key 需要重新分配 |
| 一致性哈希 | 节点环状分布,key 分布在环上 | 容易出现数据倾斜,槽位不均匀 |
| 哈希槽(Redis Cluster) | 先计算 key 对应的槽位,再决定由哪个节点负责 | 灵活、均衡、便于扩展 |
1. 哈希求余
哈希求余,就是借鉴了哈希表的基本思想,借助哈希函数,把一个 key 映射到整数,再针对数据的长度,求余就可以得到一个数组的下标。
哈希求余的分片算法具体是什么样子的呢?
设有 N 个分片,使用 [0,N-1] 这样序号进行编号,针对某个给定的 key,先计算 hash 值,再把得到的结果%N,得到的结果即为 分片编号
例如:N 为 3,给定 key 为 hello,对 hello 计算 hash 值(比如使用 md5 算法将其换为 16进制)。得到的结果为bc4b2a76b9719d91,再把这个结果%3,结果为 0,那么就把 hello 这个key放到0号分片上.

- 后续查询 key 的时候也是同样的算法。只要 key 是一样的,hash 函数式一样的,得到的分片的值就是一样的。
- 随着业务逐渐增长,数据变多的时候,仅有的几个分片就不足以保存数据了,这就需要对集群进行扩容,引入新的分片
优点:简单高效,数据分配均匀
但是这时候,哈希求余算法的缺点就体现出来了,就是一旦需要进行扩容,N 改变了,原有的映射规则被破坏,就需要让节点之间的数据相互传输,重新排列,以满足新的映射规则。此时需要搬运的数据量是比较多的,开销较大。
如下:(N从 3 => 4 重新映射)

从上图中可以看到,其中的 21 个数据,只有 3 个 key 没有被搬运,其他的 key 都是搬运过的。
当然,哈希求余的分片算法也是有一定优点的,那就是 简单高效,数据分配比较均匀。
2. 一致性哈希算法
在 哈希求余 这种操作中,当前的 key 属于哪个分片是交替的。例如某些数据的 hash 值分别是 102, 103, 104,这些数据在对 3 求余后,得到的值分别是 0, 1, 2,它们哈希求余求出的值是交替的,交替分布在不同的集群中。
而在一致性哈希算法中,我们改进了这种交替存储的方式为 连续存储。一致性哈希算法的过程如下:
- 数据空间映射:首先把数据空间全部映射到一个圆环上,数据按照顺时针方向增长。
- 分片定位:假设当前存在三个分片,将这些 分片放到圆环的某个位置。
- Key 映射与分配:对于每一个 key,通过哈希函数 计算得到 哈希值 H,并将 H 映射到圆环上的对应位置。从 H 所在的位置开始,顺时针向下找,找到的第一个分片即为该 key 所从属的分片。
这相当于 N 个分片的位置将整个圆环分成了 N 个管辖区间,key 的哈希值落在某个区间内,就归该区间管理。
在这种一致性哈希算法情况下,如果需要对数据进行扩容,原有分片在环上的位置不动,只需要在环上 安排一个新的分片(如下图粉色区域)。

此时只需把 3 号分片上的部分数据搬运给新增的分片(如 4 号分片),而其他分片管理的区间保持不变。
这种方式 相比哈希求余减少了数据搬运成本,提高了扩容操作效率,但可能导致各分片的数据量不均匀,出现 数据倾斜 现象
类比理解:高铁站取票
- 现在的高铁站都可以直接刷身份证了。但是以前的时候需要网上先购票,然后再去高铁站的取票机上把票取出来.
- 假设:一个人每次来高铁站,都会停车在同一个位置 (不同的人停车位置不同) ,每个人下车之后都往右手方向走,遇到第一个取票机就进行取票
3. 哈希槽分区算法(Redis 使用)
- 求余%:扩容不便
- 一致性分片:数据倾斜
为了解决上述问题,Redis 集群引入了哈希槽算法,Redis Cluster 是一个分布式、去中心化的 Redis 数据存储方案。 它通过以下机制实现:
- 数据分片(Sharding):将所有 key 映射到不同的节点上
- 槽位管理:使用 16384 个哈希槽(hash slots)
- 自动迁移:支持动态扩容/缩容、故障转移等操作
Redis Cluster 的数据分布流程如下:
Key → CRC16(key) → hash_slot = CRC16(key) % 16384 → 找到对应的 Node → 存储该 Key
- 这里 CRC 是一种哈希算法,16384 等于 16*1024 或者说是 2 14 2^{14} 214(相当于 2048 个字节/2KB)
- 此算法 结合了 一致性哈希 和 哈希求余 的特点
- 将哈希值映射到 16384 个槽位上,然后把这些 槽位均匀地分配给每个分片。每个分片会使用 “位图” 这样的数据结构来记录自己持有的槽位号,用 0/1 表示是否持有该槽位。
关于槽位管理机制
槽位信息保存方式:Redis 使用 位图(Bitmap) 记录每个节点负责的槽位。
例如:unsigned char my_slots[REDIS_CLUSTER_SLOTS / 8]; // 16384 / 8 = 2048 字节
- 每个 bit 表示是否拥有某个槽位
槽位同步机制:
- 每个节点定期与其他节点通信,交换槽位信息
- 当发现某个节点宕机,其他节点会接管它的槽位
- 当新增节点,旧节点会迁移部分槽位给新节点
槽位分配策略
假设当前有三个分片,一种可能的分配方式如下:
| 分片编号 | 负责槽位范围 | 槽位数量 |
|---|---|---|
| Node0 | 0 ~ 5461 | 5462 |
| Node1 | 5462 ~ 10923 | 5462 |
| Node2 | 10924 ~ 16383 | 5460 |
实际上,分片方式非常灵活,每个分片持有的 槽位号可以是连续或不连续的。尽管不是严格意义上的均匀,但差异很小,使得数据分布较为均衡。
当需要进行扩容时,比如新增一个 3 号分片,可以通过重新分配原有的槽位来实现,如下:
| 分片编号 | 负责槽位范围 | 槽位数量 |
|---|---|---|
| Node0 | 0 ~ 4095 | 4096 |
| Node1 | 5462 ~ 9557 | 4096 |
| Node2 | 10924 ~ 15019 | 4096 |
| Node3 | 4096~5461, 9558~10923, 15020~16383 | 4096(三段) |
此时,每个分片分出一部分槽位给新来的分片,并且保证最终所有分片持有的槽位个数接近,新增分片承担相同的负载,数据迁移可控(只迁移指定槽位)
此外,还有一些关于 Redis 集群的问题需要注意:
问题一:Redis 集群最多有 16384 个分片吗?
不是的,理论上 Redis 集群可以有最多 16384 个分片,但这并不推荐
- 每个分片最少一个槽位:所以最多可以有 16384 个分片
- 太多分片会增加通信成本:每个节点需要广播自己负责哪些槽位,心跳包体积增大
- 数据分布不均风险增加:如果每个分片只有一两个槽位,可能造成 key 分布严重不均
- 故障概率上升:分片越多,出错的概率也越高,运维复杂度提升
因此 Redis 官方建议不要超过 1000 个节点
问题二:为什么是16384个槽位?
因为 16384 是 2 14 2^{14} 214,足够用、够灵活、通信开销低。
- 心跳包大小限制:Redis Cluster 中,节点之间通过心跳包交换信息,其中包含该节点负责的槽位信息。16384 个槽位只需要 2KB 的位图(16384 / 8 = 2048 bytes)
- 控制网络带宽消耗:如果槽位太多(比如 65536),则每个心跳包就需要 8KB,频繁发送会影响性能
- 平衡灵活性与效率:16384 个槽位可以让每个分片持有几十甚至上百个槽位,便于均衡分布
- 适合最大分片数:Redis 作者建议最多 1000 个节点,16384 / 1000 ≈ 16,每个节点至少能分到 16 个槽位,足够平衡
- 二进制友好:16384 = 2^14,便于位运算和位图操作
问题三:在 Redis 集群中,key 是要先映射到槽位上,再映射到分片上的,为什么要这样设计
这种设计的目的是为了确保数据分布的均匀性:
- 如果每个分片包含的槽位较多,并且槽位个数相当,那么可以认为每个分片包含的 key 数量也是相等的,从而保证了数据分布的均匀性。
- 然而,如果每个分片包含的槽位非常少,槽位的数量可能无法直观地反映出 key 的数量,这种情况之下,数据的均匀性就难以保证。
- 此外,如果每个分片中包含的槽数非常少,则会导致集群中的服务器规模变得非常庞大,随着服务器数量的增加,出现故障的概率也会相应增大。
哈希槽的优势
| 特性 | 说明 |
|---|---|
| 🧱 固定槽位数量 | 16384 个槽位(2^14),方便管理和通信 |
| 🔄 动态分配 | 每个节点可以持有多个槽位,扩容时只需迁移部分槽位 |
| ⚖️ 数据分布更均匀 | 只要每个节点持有的槽位数接近,就能保证 key 数量大致相等 |
| 📦 支持主从复制与故障转移 | 每个槽位可有主从结构,确保高可用 |
| 🧭 通信效率高 | 心跳包中只需携带位图表示当前节点负责哪些槽位 |
四、Docker 搭建集群
接下来使用docker搭建一个如下集群,拓扑结构如下:

注意
- 本示例中我们将创建11个Redis节点
- 其中前9个用于演示集群的搭建,后两个用于演示集群扩容。
1. 创建目录和配置文件
首先,创建一个名为 redis-cluster 的目录,并在该目录下创建两个文件:
root@VM-8-10-ubuntu:~/redis# mkdir redis-cluster
root@VM-8-10-ubuntu:~/redis# cd redis-cluster
root@VM-8-10-ubuntu:~/redis/redis-cluster# touch docker-compose.yml
root@VM-8-10-ubuntu:~/redis/redis-cluster# touch generate.sh
编写 generate.sh 脚本:此脚本将为每个 Redis 实例创建配置文件并设置不同的 IP 地址:
#!/bin/bash
# 创建并配置前9个节点
for port in $(seq 1 9); do
mkdir -p redis${port}
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.10${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
requirepass 123456
masterauth 123456
EOF
done
# 创建并配置第10和11个节点(用于后续扩容)
for port in $(seq 10 11); do
mkdir -p redis${port}
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
requirepass 123456
masterauth 123456
EOF
done
C++ 和 Java 中的循环
- C++:range based for(C++ 也有 std::for_each,STL 中的一个函数)
- Java: for each
Shell 脚本中的循环如上,解释如下:
seq: 一个 Linux 命令,生成 [1, 9]。\: 续行符,把下一行的内容和当前行合并成一行。- 默认情况下,shell 要求把所有的代码都写到一行里,使用续行符来换行。
{ }: 在 shell 中表示变量,不是表示代码块。- 对于 for,使用 do 和 done 来表示代码块的开始和结束。
- 循环体内的代码。
- 字符串拼接:在 shell 中拼接字符串是直接写到一起,而不需要使用 +。
预期效果
- 得到 11 个目录,每个目录里都有一个配置文件。
- 配置文件中,IP 地址各不相同。
注意:cluster-announce-ip 172.30.0.10${port} 的值对于每个实例是不同的,以确保它们在网络中的唯一性。
执行生成命令
运行以下命令以执行 generate.sh 脚本,这将在 redis-cluster/ 目录下生成相应的子目录及配置文件:
bash generate.sh
# 此时生成的目录结构如下
root@VM-8-10-ubuntu:~/redis/redis-cluster# tree .
.
├── docker-compose.yml
├── generate.sh
├── redis1
│ └── redis.conf
├── redis10
│ └── redis.conf
├── redis11
│ └── redis.conf
├── redis2
│ └── redis.conf
├── redis3
│ └── redis.conf
├── redis4
│ └── redis.conf
├── redis5
│ └── redis.conf
├── redis6
│ └── redis.conf
├── redis7
│ └── redis.conf
├── redis8
│ └── redis.conf
└── redis9
└── redis.conf
每个 redis.conf 文件的内容除了 cluster-announce-ip 不同外,其他部分都相同。例如,redis1/redis.conf 包含如下内容:
root@VM-8-10-ubuntu:~/redis/redis-cluster# cat redis1/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.101
cluster-announce-port 6379
cluster-announce-bus-port 16379
requirepass 123456
masterauth 123456
配置说明:
cluster-enabled yes: 开启集群模式。cluster-config-file nodes.conf: 集群节点自动生成的配置文件。cluster-node-timeout 5000: 节点失联的超时时间(毫秒)。cluster-announce-ip 172.30.0.101: 节点自身的 IP 地址。cluster-announce-port 6379: 节点自身的业务端口。cluster-announce-bus-port 16379: 节点自身的总线端口,用于集群管理信息交互。
注意:
cluster-announce-ip: Docker 容器的 IP 地址。port: 容器内的 Redis 端口。- 业务端口: 用于业务数据通信。(eg. 客户端访问的
- 管理端口: 用于管理任务通信。
故障处理:如果某个分片中的 Redis 主节点挂了,就需要让从节点成为主节点,就需要通过刚才 管理端口 来完成相应的操作。
- 服务器端口绑定: 一个服务器可以绑定多个端口号。
2. 编写 docker-compose.yml
接下来,我们需要编写 docker-compose.yml 文件来定义 Docker 容器网络和服务,以下是配置示例:
networks:
mynet:
ipam:
config:
- subnet: 172.30.0.0/24
services:
redis1:
image: 'redis:5.0.9'
container_name: redis1
restart: always
volumes:
- ./redis1/:/etc/redis/
ports:
- 6371:6379
- 16371:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.101
redis2:
image: 'redis:5.0.9'
container_name: redis2
restart: always
volumes:
- ./redis2/:/etc/redis/
ports:
- 6372:6379
- 16372:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.102
redis3:
image: 'redis:5.0.9'
container_name: redis3
restart: always
volumes:
- ./redis3/:/etc/redis/
ports:
- 6373:6379
- 16373:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.103
redis4:
image: 'redis:5.0.9'
container_name: redis4
restart: always
volumes:
- ./redis4/:/etc/redis/
ports:
- 6374:6379
- 16374:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.104
redis5:
image: 'redis:5.0.9'
container_name: redis5
restart: always
volumes:
- ./redis5/:/etc/redis/
ports:
- 6375:6379
- 16375:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.105
redis6:
image: 'redis:5.0.9'
container_name: redis6
restart: always
volumes:
- ./redis6/:/etc/redis/
ports:
- 6376:6379
- 16376:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.106
redis7:
image: 'redis:5.0.9'
container_name: redis7
restart: always
volumes:
- ./redis7/:/etc/redis/
ports:
- 6377:6379
- 16377:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.107
redis8:
image: 'redis:5.0.9'
container_name: redis8
restart: always
volumes:
- ./redis8/:/etc/redis/
ports:
- 6378:6379
- 16378:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.108
redis9:
image: 'redis:5.0.9'
container_name: redis9
restart: always
volumes:
- ./redis9/:/etc/redis/
ports:
- 6379:6379
- 16379:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.109
redis10:
image: 'redis:5.0.9'
container_name: redis10
restart: always
volumes:
- ./redis10/:/etc/redis/
ports:
- 6380:6379
- 16380:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.110
redis11:
image: 'redis:5.0.9'
container_name: redis11
restart: always
volumes:
- ./redis11/:/etc/redis/
ports:
- 6381:6379
- 16381:16379
command:
redis-server /etc/redis/redis.conf
networks:
mynet:
ipv4_address: 172.30.0.111
网络配置
subnet: 此处为了后续创建静态的 IP,就需要先手动创建出网络,同时给这个网段分配 IP。这里分配的 网络号是 172.30.0,其中这个网络号是私网格式的一种,需要注意的是,不能和当前主机上现有的其他网段冲突,0/24 是主机号。volumes: 配置文件映射,确保容器内的配置文件与宿主机上的配置文件同步更新。ports: 端口映射,需要注意的是,映射右边的端口必须和配置文件中的保持一致。ipv4_address: 静态配置的网络 IP,这里的 IP 也必须和之前的配置文件中的一致。
注意:启动容器之前,要把之前测试的容器关掉,避免冲突
docker rm -f $(docker ps -aq) # 一键删除所有容器
root@VM-8-10-ubuntu:~/redis/redis-cluster# ps aux | grep redis
root 2713195 0.0 0.0 6480 2236 pts/1 S+ 11:28 0:00 grep --color=auto redis
root@VM-8-10-ubuntu:~/redis/redis-cluster# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
启动容器:docker compose up -d
root@VM-8-10-ubuntu:~/redis/redis-cluster# docker compose up -d
[+] Running 12/12
✔ Network redis-cluster_mynet Created 0.1s
✔ Container redis7 Started 2.4s
✔ Container redis9 Started 2.7s
✔ Container redis4 Started 3.1s
✔ Container redis2 Started 1.9s
✔ Container redis5 Started 2.6s
✔ Container redis3 Started 2.3s
✔ Container redis6 Started 2.8s
✔ Container redis10 Started 3.0s
✔ Container redis1 Started 2.9s
✔ Container redis11 Started 3.3s
✔ Container redis8 Started 3.2s
root@VM-8-10-ubuntu:~/redis/redis-cluster# ps aux | grep redis
lxd 2713772 0.1 0.1 40712 4856 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
lxd 2713786 0.1 0.1 40712 4724 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
lxd 2713810 0.1 0.1 40712 4764 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
lxd 2713856 0.1 0.1 40712 4852 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
lxd 2713901 0.1 0.1 40712 4788 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
lxd 2713946 0.1 0.1 40712 4876 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
lxd 2713968 0.1 0.1 40712 4792 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
lxd 2713992 0.1 0.1 40712 4860 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
lxd 2714001 0.1 0.1 40712 4788 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
lxd 2714008 0.1 0.1 40712 4712 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
lxd 2714018 0.1 0.1 40712 4784 ? Ssl 11:29 0:00 redis-server 0.0.0.0:6379 [cluster]
root 2715119 0.0 0.0 6612 2200 pts/1 S+ 11:30 0:00 grep --color=auto redis
3. 构建集群
接下来需要把这些服务容器全部构建为集群,使用如下命令:
redis-cli --cluster create 172.30.0.101:6379 172.30.0.102:6379 172.30.0.103:6379 172.30.0.104:6379 172.30.0.105:6379 172.30.0.106:6379 172.30.0.107:6379 172.30.0.108:6379 172.30.0.109:6379 --cluster-replicas 2
--cluster create表示建立集群,后面填写每个节点的 IP 和端口。--cluster-replicas 2表示每个主节点需要两个从节点备份。设置了这个配置之后,Redis 就会知道,每 3 个节点是一组(一个分片)。但是分配的时候,谁是主节点,谁是从节点,都是随机的。
这样redis就会依据规则自动构建集群,每三个节点构成一个分片(因为指定了一个主有两个从),并且会自动构建主从关系。
输入命令后,出现以下界面:

- 第 1 部分,是信号槽的分配部分,可以看到redis内部采用了连续分配的方式,将信号槽均匀的分配给三个分片。
- 第 2 部分,是主从关系的建立部分,第一行表示:172.30.0.105:6379成为172.30.0.101:6379的从节点。那么这六行日志,就表示xxx.101、xxx.102、xxx.103成为了三个主节点,其余的节点成为这三个的从节点。
- 第 3 部分,是一段日志,就是具体把那一部分槽位具体分配给哪一个分片,比如xxx.101拿到了[0-5460]的槽位号。
输入 yes 之后才会真正创建,见到 [OK] 之后说明集群建立完成。
连接并验证集群
使用客户端连接集群中的任何一个节点,都相当于连上了整个集群:
redis-cli -h 172.30.0.101 -p 6379
在客户端中,我们使用 cluster nodes 来查看当前集群的信息:
172.30.0.101:6379> cluster nodes
06353c7d564e3a57d84ce8c9ea4a168f44fa5c12 172.30.0.108:6379@16379 slave 29a37693aede6c4b163b32ea19fb2e9d14254c36 0 1752291757000 8 connected
98f3f58d82b8533fd7d7b59ed893c849aff16d45 172.30.0.106:6379@16379 slave 07225d84eefabeee02f8c5aebb3a91bb9ed795fe 0 1752291757000 6 connected
7919750d6c26e8896e49f1251291e7096b0b4b28 172.30.0.103:6379@16379 master - 0 1752291756509 3 connected 10923-16383
47acaa1a1ce63be503b768f8cb81231beb3afbce 172.30.0.107:6379@16379 slave 29a37693aede6c4b163b32ea19fb2e9d14254c36 0 1752291757111 7 connected
8688f3d23b67df9d99cda04092edcdaf10416290 172.30.0.109:6379@16379 slave 7919750d6c26e8896e49f1251291e7096b0b4b28 0 1752291757000 9 connected
39fff311ba4a61333be46d04523334c0d006ee0f 172.30.0.105:6379@16379 slave 07225d84eefabeee02f8c5aebb3a91bb9ed795fe 0 1752291757511 5 connected
07225d84eefabeee02f8c5aebb3a91bb9ed795fe 172.30.0.101:6379@16379 myself,master - 0 1752291756000 1 connected 0-5460
29a37693aede6c4b163b32ea19fb2e9d14254c36 172.30.0.102:6379@16379 master - 0 1752291758014 2 connected 5461-10922
b1089bc3b33e9ec9662294830adaeb70a39151cf 172.30.0.104:6379@16379 slave 7919750d6c26e8896e49f1251291e7096b0b4b28 0 1752291757612 4 connected
重定向:尝试插入数据
172.30.0.101:6379> set key1 111
(error) MOVED 9189 172.30.0.102:6379
- 报错了,因为当前集群发生了分片,每个分片都只能存储一部分数据,
key1经过哈希运算,发现并不是这个节点可以存储的值,于是就报错了。
想要解决这个问题,可以在启动时加上-c选项,此时插入数据,会自动重定向到对应的节点。
root@VM-8-10-ubuntu:~/redis/redis-cluster# redis-cli -c -p 6371 -a 123456
127.0.0.1:6371> set key1 111
-> Redirected to slot [9189] located at 172.30.0.102:6379
OK
172.30.0.102:6379> set key2 222
-> Redirected to slot [4998] located at 172.30.0.101:6379
OK
172.30.0.101:6379> get key1
-> Redirected to slot [9189] located at 172.30.0.102:6379
"111"
172.30.0.102:6379> mget key1 key2
(error) CROSSSLOT Keys in request don't hash to the same slot
- 如上每次操作数据,如果该数据不属于当前分片,就会触发一次 重定向,自动跳转到对应的客户端。命令行前面的 ip 一直在改变,这就说明我们的客户端一直在切换。
什么会出现 CROSSSLOT 错误?
这是 Redis Cluster 的一个设计限制,Redis Cluster 的核心原则如下:
在 Redis Cluster 中,所有的读写操作必须针对同一个哈希槽(slot),否则就会报错
CROSSSLOT Keys in request don't hash to the same slot
- 原因:两个 key 分属不同槽位 → Redis Cluster 不允许跨槽批量操作
- 因此在集群的情况下,最好不要一次性操作多个key。
其实我们在测试上面的时候,如果有密码最好是在启动的时候带入,而不是启动之后再写入,如下:
root@VM-8-10-ubuntu:~/redis/redis-cluster# redis-cli -c -p 6379
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> set key1 111
-> Redirected to slot [9189] located at 172.30.0.102:6379
(error) NOAUTH Authentication required.
# 解决办法
redis-cli -c -p 6379 -a 123456
- 因为 MOVED 重定向,也会自动携带密码访问新节点
五、主节点宕机自动处理流程
如果在集群中,某一个分片的主节点宕机了,会发生什么?在部署集群时,并没有引入哨兵节点,但是集群也会完成哨兵的工作,如果主节点宕机了,集群会自动完成重新选主的过程。
接下来就讲解集群中是如何完成故障转移的。
故障判定:Redis 集群中的所有节点会周期性地使用心跳包进行通信,确保各节点之间的连接状态和集群配置信息的同步。以下是心跳包交互的具体过程:
- 心跳包通信:节点A给节点B发送ping包,B接收到后返回一个pong包。这些消息除了类型属性外,其余部分相同,并包含以下集群配置信息:
- 节点ID
- 所属分片
- 主/从角色
- 附属主节点(如果为从节点)
- 持有的哈希槽位图
- 随机化心跳检测:每个节点每秒钟随机选择一部分节点发送ping包,而非向所有节点发送,以减少大量节点存在时的心跳包数量。
- PFAIL 状态:当节点A尝试与节点B通信未果时,A首先尝试重置TCP连接;若仍然失败,则将B标记为PFAIL(主观下线)状态。
- Gossip协议确认:一旦A判定B为PFAIL,它会通过内置的Gossip协议与其他节点沟通,确认B的状态。每个节点维护自己的“下线列表”,反映其视角下的故障节点。
- FAIL 状态:如果多数节点(超过半数)也认为B处于PFAIL状态,那么A将正式把B标记为FAIL(客观下线),并通知其他节点更新状态。
故障迁移:当一个节点被标记为FAIL时,根据该节点的角色,可能触发不同的响应:
- 从节点故障:如果是从节点,通常不需要特别处理,因为它们不直接服务客户端请求。
- 主节点故障:如果故障节点是主节点,则需要进行故障迁移,流程如下:
资格审查:从节点检查自身是否符合晋升条件,即与原主节点的最后通信时间不超过设定阈值。 - 休眠等待:符合条件的从节点进入一段随机的休眠期,休眠时间=500ms基础时间+[0,500ms]随机时间+排名*1000ms,offset 的值越大,则排名越靠前(越小)。这一策略有助于防止多个从节点同时竞选。
- 拉票选举:休眠结束后的 从节点开始向其他集群的主节点拉票。只有主节点有投票权,且每个主节点只能投一票。
- 晋升为主:获得超过半数主节点支持的从节点晋升为主节点,并执行
SLAVEOF NO ONE命令脱离原主节点控制。并且让同一分片中的其它节点执行 slaveof - 最后,新的主节点会把自己成为主节点的消息,同步给其他集群的节点,大家也都会更新自己保存的集群结构信息
此过程类似于 Raft算法,旨在选出网络状况较好的节点作为新的主节点,保证集群的服务连续性和数据一致性。
Fail 状态的影响:在某些情况下,单个节点的故障可能导致整个集群进入fail状态,具体情形包括但不限于:
- 某一分片内的所有主节点和从节点都不可用。
- 某一分片内主节点故障且 无可用从节点可晋升。
- 超过半数的主节点失效。
演示如下:

通过
docker stop redis1,关掉了redis1节点,也就是xxx.101下线了,而这是一个主节点。登录
6372端口的客户端,查看当前集群,可以发现xxx.105成为了新的主节点,而xxx.105原先是xxx.101的从节点。
然后再重启redis1,其变为了reids5的从节点,如下:

此处 集群的故障转移,和哨兵的故障转移是有一些差别的
- 哨兵:sdown/odown
- 集群:PFAIL/FAIL
六、集群扩容
步骤一:加入集群
命令格式:使用 --cluster add-node 选项将新节点添加到集群。
具体操作:
- add-node 后的第一组地址是 新增节点的地址,第二组地址是 集群中的任意节点地址
redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379

通过上述命令,可以将
xxx.110节点加入到集群中。登录任意客户端查看,可以看到集群内部已有xxx.110,且是一个主节点。但需要注意的是,新增节点没有分配哈希槽。
步骤二:分配哈希槽
命令格式:使用 --cluster reshard 选项给新节点分配哈希槽(注意是 reshard 不是 shared)
redis-cli --cluster reshard 172.30.0.101:6379
执行之后,会进入交互式操作。然后 Redis 会提示用户输入一下内容:
- 系统会询问多少个 slots(哈希槽)需要 reshared(此处填写 4096)
- 接着询问将这些哈希槽移动给哪个节点(此处填写172.30.0.110 这个节点的集群节点id)
- 最后询问从哪些节点中空出这些哈希槽(可以 选择
all表示从所有现有节点平均提取,或自己指定节点ID 然后以 done 结束)
How many slots do you want to move (from 1 to 16384)? 4096
What is the receiving node ID? 522a1bd88a1a9084e6919fa88f4bf1c3655ad837
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1: all
执行完毕后,进入任意客户端查看集群现状,可以看到新节点获得了三个范围的哈希槽

问题:搬运哈希槽的过程可能比较久,在此期间用户的访问合法性如何?
- 大部分哈希槽在搬运过程中仍可正常访问。
- 如果 用户访问正在移动的哈希槽,则可能会导致访问失败。
步骤三:添加从节点
命令格式:使用 add-node 命令配合 --cluster-slave 和 --cluster-master-id 选项来添加从节点。
redis-cli --cluster add-node 新节点 --cluster-slave --cluster-master-id 主节点的ID
参数说明:
--cluster-slave:指定新添加的节点作为从节点。--cluster-master-id:后面跟着的是主节点的ID,表示该节点从属于哪一个节点。
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id 0c2549f318046f521c62c29b60eb60f0ae7551a4

此时扩容目标初步达成,但是为了保证整个集群的可用性,还需要给主节点添加一些从结点,保证主节点宕机之后有后继节点接班
集群缩容(少见)
扩容是比较常见的,但是缩容其实非常少见,此处我们简单了解缩容的操作步骤即可接下来演示把 110 和 111 这两个节点删除,
步骤一:删除从节点
语法:redis-cli --cluster del-node [集群中任⼀节点ip:port] [要删除的从机节点 nodeId]
- 此处删除的节点 nodeld 是 111 节点的 id.
root@VM-8-10-ubuntu:~/redis/redis-cluster# redis-cli --cluster del-node 172.30.0.101:6379 4504bc6b277cdc72b5eae889a701e326802df4f7
>>> Removing node 4504bc6b277cdc72b5eae889a701e326802df4f7 from cluster 172.30.0.101:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> Sending CLUSTER RESET SOFT to the deleted node.
root@VM-8-10-ubuntu:~/redis/redis-cluster#
步骤二:重新分配 slots
第一次重分配:分配给 103 1365个slots,接收slots的nodeld填写 101的nodeld.Source Node填写 110 的nodeld
How many slots do you want to move (from 1 to 16384)? 1365
What is the receiving node ID? 3397c6364b43dd8a8d49057ad37be57760d3a81f
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1: 7c343b7e3f82f2e601ac6b9eba9f846b3065c600
Source node #2: done
同理,第二次重分配给 102 1365个 slots,第二次分配给 105 1366 个 slots

- 注意:重新分配的时候需要保证该节点是主节点
此时查看集群状态,可以看到 110 节点已经不再持有 slots 了

步骤三:删除主节点
把 110 节点从集群中删除
root@VM-8-10-ubuntu:~/redis/redis-cluster# redis-cli --cluster del-node 172.30.0.101:6379 0c2549f318046f521c62c29b60eb60f0ae7551a4 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Removing node 0c2549f318046f521c62c29b60eb60f0ae7551a4 from cluster 172.30.0.101:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> Sending CLUSTER RESET SOFT to the deleted node.
# 查看集群节点信息, 110 节点不在集群了
root@VM-8-10-ubuntu:~/redis/redis-cluster# redis-cli -p 6371 -a 123456 cluster nodes
主从复制:分担了读取压力
哨兵:监控,稳健性提高
集群:存储数据量 提升
【★,°:.☆( ̄▽ ̄)/$:.°★ 】那么本篇到此就结束啦,如果有不懂 和 发现问题的小伙伴可以在评论区说出来哦,同时我还会继续更新关于【Redis】的内容,请持续关注我 !!

更多推荐




所有评论(0)