redis cluster(去中心化)
- [一、Redis 集群介绍](#一、Redis 集群介绍)
- 二、实现Redis集群
一、Redis 集群介绍
1、背景
从最开始的一主N从,读写分离,再到Sentinel哨兵机制,单实例的Redis缓存足以应对大多数的使用场景,也能实现主从故障迁移。
但是,在某些场景下,单实例存Redis缓存会存在的几个问题:
(1)写并发:Redis单实例读写分离可以解决读操作的负载均衡,但对于写操作,仍然是全部落在了master节点上面,在海量数据高并发场景,一个节点写数据容易出现瓶颈,造成master节点的压力上升。
(2)海量数据的存储压力: 单实例Redis本质上只有一台Master作为存储,如果面对海量数据的存储,一台Redis服务器就应付不过来了,而且数据量太大意味着持久化成本高,严重时可能会阻塞服务器,造成服务请求成功率下降,降低服务的稳定性。
针对以上的问题,Redis集群提供了较为完善的方案,解决了存储能力受到单机限制,写操作无法负载均衡的问题。
2、概念
Redis3.0加入了Redis的集群模式,通过数据的分布式存储实现去中心化的思想。redis通过对数据进行分片,将不同的数据存储在不同的master节点上面,从而解决了海量数据的存储问题。Redis也内置了高可用机制,支持N个master节点,每个master节点都可以挂载多个slave节点,当master节点挂掉时,集群会提升它的某个slave节点作为新的master节点。
redis集群没有中心节点的说法,对于客户端来说,整个集群可以看成一个整体,可以连接任意一个节点进行操作,就像操作单一Redis实例一样,不需要任何代理中间件,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node。
官网文档地址:https://redis.io/docs/manual/scaling/
3、优势
- 自动分割数据到不同的节点上。
- 整个集群的部分节点失败或者不可达的情况下能够继续处理命令。

4、数据分布式存储
Redis 集群引入了哈希槽(slots)实现数据的分布式存储。
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽(slot = CRC16(key)%16384)。集群的每个节点负责一部分hash槽,比如当前集群有3个节点,那么:
- 节点 A 包含 0 到 5460号哈希槽。
- 节点 B 包含5461 到 10922号哈希槽。
- 节点 C 包含10923 到 16383号哈希槽。
这种结构很容易添加或者删除节点。比如想新添加个节点D,只需要从节点 A、B、C中得到部分哈希槽并分配到D上即可。 如果想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可。由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
!Warning
假设具有A、B、C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用(注意:整个集群都不可用)
然而如果在集群创建的时候(或者过一段时间)为每个节点添加一个从节点A1、B1、C1,那么整个集群便有三个master节点和三个slave节点组成,这样在节点B失败后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了。
不过当B和B1 都失败后,集群是不可用的。
二、实现Redis集群
1、项目环境
主机说明 | 主机IP | 端口 |
---|---|---|
master1 | 192.168.83.20 | 6379 |
slave1 | 192.168.83.20 | 6380 |
master2 | 192.168.83.21 | 6379 |
slave2 | 192.168.83.21 | 6380 |
master3 | 192.168.83.22 | 6379 |
slave3 | 192.168.83.22 | 6380 |
2、配置集群
2.1 Redis主机配置
编辑主配置文件
bash
#1、编辑主配置文件
[root@master1 ~]# vim /etc/redis/redis.conf
port 6379 # 修改端口号
pidfile /var/run/redis_6379.pid # 修改pid文件名
dir /var/lib/redis # 持久化文件存放目录
dbfilename dump_6379.rdb # 修改持久化文件名
bind 0.0.0.0 # 绑定地址
daemonize yes # 让redis后台运行
protected-mode no # 关闭保护模式
logfile /var/log/redis/redis_6379.log # 指定日志
cluster-enabled yes # 开启集群功能
cluster-config-file nodes-6379.conf #设定节点配置文件名,不需要我们创建,由redis自己维护
cluster-node-timeout 10000 # 节点心跳失败的超时时间,超过该时间(毫秒),集群自动进行主从切换
# 启动第一个redis实例
[root@master1 ~]# systemctl restart redis
# 查看端口号
[root@master1 ~]# ss -lntup | grep redis
tcp LISTEN 0 511 0.0.0.0:6379 0.0.0.0:* users:(("redis-server",pid=34021,fd=6))
127.0.0.1:6379> set haha 1
(error) MOVED 3662 192.168.83.20:6379
# 未开启集群
# 加上-c表示启用集群模式
[root@master1 ~]# redis-cli -c
127.0.0.1:6379> set master cluster
-> Redirected to slot [5565] located at 192.168.83.21:6379
OK
在该主机上启动另外一个6380端口的实例
mysql
#1、在该主机上启动另外一个6380端口的实例
[root@master1 ~]# cp /etc/redis/redis.conf /etc/redis/redis-6380.conf
#2、将6379都修改为6380
#方法一
[root@master1 ~]# vim /etc/redis/redis-6380.conf
# 进入命令端:%s/6379/6380/g修改
#方法二
sed -i 's/6379/6380/ ' /etc/redis/redis-6380.conf
#3、启用6380端口
[root@master1 ~]# redis-server /etc/redis/redis-6380.conf
# 使用命令启动6380实例并将其放在后台
[root@master1 ~]# redis-server /etc/redis/redis-6380.conf &
#4、查看是否有两个redis实例
[root@master1 ~]# ss -lntup | grep redis
tcp LISTEN 0 511 0.0.0.0:6380 0.0.0.0:* users:(("redis-server",pid=34098,fd=6))
tcp LISTEN 0 511 0.0.0.0:6379 0.0.0.0:* users:(("redis-server",pid=34021,fd=6))

将文件复制到两外两台主机依次启动
bash
#1、复制文件到master2和master3
[root@master1 ~]# scp /etc/redis/redis.conf /etc/redis/redis---6380.conf root@192.168.83.21:/etc/redis
root@192.168.83.21's password:
redis.conf 100% 92KB 43.0MB/s 00:00
redis-6380.conf 100% 92KB 32.7MB/s 00:00
[root@master1 ~]# scp /etc/redis/redis.conf /etc/redis/redis-6380.conf root@192.168.83.22:/etc/redis
#2、master2上操作
[root@master2 ~]# systemctl restart redis
[root@master2 ~]# redis-server /etc/redis/redis-6380.conf &
#3、查看redis是否运行
[root@master2 ~]# ss -lntup | grep redis
tcp LISTEN 0 511 0.0.0.0:6379 0.0.0.0:* users:(("redis-server",pid=33931,fd=6))
tcp LISTEN 0 511 0.0.0.0:6380 0.0.0.0:* users:(("redis-server",pid=33989,fd=6))
#4、master3重复上述操作
启动好全部 Redis 服务器后,接下来就是如何把这 6 个服务器按预先规划的结构来组合成集群了。在做接下来的操作之前,一定要先确保所有 Redis 实例都已经成功启动,并且对应实例的节点配置文件都已经成功生成。
bash
# 使用如下命令在所有主机上都可以看到所有实例生成的rdb文件和nodes文件
# ls /var/lib/redis/
dump_6380.rdb dump.rdb nodes-6379.conf nodes-6380.conf
2.2 创建集群命令格式:
bash
redis-cli --cluster create --cluster-replicas 副本数 主机IP:端口号 主机IP:端口号
#create 创建集群
#`节点总数 ÷ (replicas + 1)` 得到的就是master的数量。节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master。(Redis 的分配原则是:尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。)
创建集群:
bash
[root@master1 ~]# redis-cli --cluster create --cluster-replicas 1 192.168.83.20:6379 192.168.83.21:6379 192.168.83.22:6379 192.168.83.20:6380 192.168.83.21:6380 192.168.83.22:6380
# 最后成功输出如下
>>> Performing Cluster Check (using node 192.168.83.20:6379)
M: 49abc1fe5b4b8c393df995d64dc79046ff9c16d6 192.168.83.20:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 6aa30ba97bbe310961aede67ef1f340c74d6ff68 192.168.83.22:6380
slots: (0 slots) slave
replicates 3dd8e51de962edf42476e65bf636817c6fb13b68
M: 77f8f27f62b0c9e598041b3839786f23b8e53e63 192.168.83.22:6379
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: 3dd8e51de962edf42476e65bf636817c6fb13b68 192.168.83.21:6379
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: d728c8b7e5938ff96bc4b271c019fc7094b1607d 192.168.83.20:6380
slots: (0 slots) slave
replicates 77f8f27f62b0c9e598041b3839786f23b8e53e63
S: 72fdbc7928adc57673010ebf68f204f98a641646 192.168.83.21:6380
slots: (0 slots) slave
replicates 49abc1fe5b4b8c393df995d64dc79046ff9c16d6
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

查看集群状态
bash
[root@master1 ~]# redis-cli -p 6379 cluster nodes
49abc1fe5b4b8c393df995d64dc79046ff9c16d6 192.168.83.20:6379@16379 myself,master - 0 1757573878000 1 connected 0-5460
6aa30ba97bbe310961aede67ef1f340c74d6ff68 192.168.83.22:6380@16380 slave 3dd8e51de962edf42476e65bf636817c6fb13b68 0 1757573877790 2 connected
77f8f27f62b0c9e598041b3839786f23b8e53e63 192.168.83.22:6379@16379 master - 0 1757573876000 3 connected 10923-16383
3dd8e51de962edf42476e65bf636817c6fb13b68 192.168.83.21:6379@16379 master - 0 1757573878797 2 connected 5461-10922
d728c8b7e5938ff96bc4b271c019fc7094b1607d 192.168.83.20:6380@16380 slave 77f8f27f62b0c9e598041b3839786f23b8e53e63 0 1757573877000 3 connected
72fdbc7928adc57673010ebf68f204f98a641646 192.168.83.21:6380@16380 slave 49abc1fe5b4b8c393df995d64dc79046ff9c16d6 0 1757573877000 1 connected
2.3 测试
bash
#连接集群存储数据
[root@master1 ~]# redis-cli -p 6379
127.0.0.1:6379> set cluster 3
(error) MOVED 14041 192.168.83.22:6379 #结果报错
########说明######
#在集群环境下,由于 redis-cli 每次录入、查询键值时,Redis 都会计算出该 key 对应的插槽值,并交给对应插槽所在的节点进行处理。如果不是该客户端对应服务器的插槽 Redis 会报错,并告知应前往的 Redis 实例地址和端口。
##########说明结束######
#加上-c表示启用集群模式
[root@master1 ~]# redis-cli -p 6379 -c
192.168.83.22:6379> set cluster zhangjin
192.168.83.22:6379> get cluster
"zhangjin"
# 查看集群信息
192.168.168.22:6379> cluster nodes
77f8f27f62b0c9e598041b3839786f23b8e53e63 192.168.83.22:6379@16379 myself,master - 0 1757574171000 3 connected 10923-16383
3dd8e51de962edf42476e65bf636817c6fb13b68 192.168.83.21:6379@16379 master - 0 1757574171079 2 connected 5461-10922
6aa30ba97bbe310961aede67ef1f340c74d6ff68 192.168.83.22:6380@16380 slave 3dd8e51de962edf42476e65bf636817c6fb13b68 0 1757574172790 2 connected
d728c8b7e5938ff96bc4b271c019fc7094b1607d 192.168.83.20:6380@16380 slave 77f8f27f62b0c9e598041b3839786f23b8e53e63 0 1757574170000 3 connected
49abc1fe5b4b8c393df995d64dc79046ff9c16d6 192.168.83.20:6379@16379 master - 0 1757574170775 1 connected 0-5460
72fdbc7928adc57673010ebf68f204f98a641646 192.168.83.21:6380@16380 slave 49abc1fe5b4b8c393df995d64dc79046ff9c16d6 0 1757574171783 1 connected
3、故障转移
模拟master1宕机
bash
# 模拟master1宕机
[root@master1 ~]# redis-cli -p 6379
127.0.0.1:6379> shutdown
not connected>
[root@master1 ~]# redis-cli -p 6380
127.0.0.1:6380> CLUSTER nodes
# 可以看到master切换到了192.168.168.21:6380

再次启动master1
bash
[root@master1 ~]# systemctl start redis
[root@master1 ~]# redis-cli -p 6379
127.0.0.1:6379> cluster nodes
# 可以发现master1变成了从

!Important
问:如果所有某段插槽的主从节点都宕机了,Redis 服务是否还能继续?
答:当发生某段插槽的主从都宕机后,如果在 redis.conf 配置文件中的 cluster-require-full-coverage 参数的值为 yes ,那么整个集群都挂掉;如果参数的值为 no ,那么该段插槽数据全都不能使用,也无法存储。
4、集群扩容
4.1 我们向现有集群中添加两个节点,这两个节点做一主一从。主节点的端口号为 6381,从节点的端口号为 6382。
bash
[root@master1 ~]# cp /etc/redis/redis.conf /etc/redis/redis-6381.conf
[root@master1 ~]# cp /etc/redis/redis.conf /etc/redis/redis-6382.conf
[root@master1 ~]# sed -i 's/6379/6381/ ' /etc/redis/redis-6381.conf
[root@master1 ~]# sed -i 's/6379/6382/ ' /etc/redis/redis-6382.conf
# 启动实例
[root@master1 ~]# redis-server /etc/redis/redis-6381.conf &
[2] 28795
[root@master1 ~]# redis-server /etc/redis/redis-6382.conf &
[3] 28800
[root@master1 ~]# ss -lntup | grep redis
tcp LISTEN 0 511 0.0.0.0:6379 0.0.0.0:* users:(("redis-server",pid=2491,fd=6))
tcp LISTEN 0 511 0.0.0.0:6381 0.0.0.0:* users:(("redis-server",pid=2560,fd=6))
tcp LISTEN 0 511 0.0.0.0:6380 0.0.0.0:* users:(("redis-server",pid=2404,fd=6))
tcp LISTEN 0 511 0.0.0.0:6382 0.0.0.0:* users:(("redis-server",pid=2566,fd=6))
4.2 添加节点到集群的语法格式为:
bash
redis-cli --cluster add-node new_host:new_port existing_host:existing_port
--cluster-slave
--cluster-master-id <arg>
add-node命令用于添加节点到集群中,参数说明如下:
- new_host:被添加节点的主机地址
- new_port:被添加节点的端口号
- existing_host:目前集群中已经存在的任一主机地址
- existing_port:目前集群中已经存在的任一端口地址
- --cluster-slave:用于添加从(Slave)节点
- --cluster-master-id:指定主(Master)节点的ID(唯一标识)字符串
添加主节点
bash
[root@master1 ~]# redis-cli --cluster add-node 192.168.83.20:6381 192.168.83.20:6379
添加从节点
bash
#需要先查看6381节点的ID值
[root@master1 ~]# redis-cli -p 6381
127.0.0.1:6381> CLUSTER nodes
49402647b0a7a55d0b00b187cf56befdd16c56c7 192.168.83.20:6381@16381 myself,master - 0 1757575355000 0 connected
# 将192.168.83.20:6382作为拥有此ID的49402647b0a7a55d0b00b187cf56befdd16c56c7的slave身份加入192.168.83.20:6379所在集群
[root@master1 ~]# redis-cli --cluster add-node 192.168.83.20:6382 192.168.83.20:6379 --cluster-slave --cluster-master-id 49402647b0a7a55d0b00b187cf56befdd16c56c7
由于集群中增加了新节点,需要对现有数据重新进行分片操作。重新分片的语法如下:
bash
redis-cli --cluster reshard host:port
--cluster-from <arg>
--cluster-to <arg>
--cluster-slots <arg>
--cluster-yes
--cluster-timeout <arg>
--cluster-pipeline <arg>
--cluster-replace
reshard命令用于重新分片,参数说明如下:
- host:集群中已经存在的任意主机地址
- port:集群中已经存在的任意主机对应的端口号
- --cluster-from:表示slot目前所在的master节点node ID,多个ID用逗号分隔
- --cluster-to:表示需要分配节点的node ID
- --cluster-slot:分配的slot数量
- --cluster-yes:指定迁移时的确认输入
- --cluster-timeout:设置migrate命令的超时时间
- --cluster-pipeline:定义cluster getkeysinslot命令一次取出的key数量,不传的话使用默认值为10
- --cluster-replace:是否直接replace到目标节点
bash
#分配100个插槽
[root@master1 ~]# redis-cli --cluster reshard 192.168.83.21:6379 --cluster-from 3dd8e51de962edf42476e65bf636817c6fb13b68 --cluster-to 49402647b0a7a55d0b00b187cf56befdd16c56c7 --cluster-slots 200 --cluster-yes --cluster-timeout 10000 --cluster-pipeline 10 --cluster-replace

可以查看插槽分配情况
bash
[root@master1 ~]# redis-cli -p 6379
127.0.0.1:6379> cluster nodes

5、集群缩容
添加节点的时候是先添加node节点到集群,然后分配槽位,删除节点的操作与添加节点的操作正好相反,是先将被删除的Redis node上的槽位迁移到集群中的其他Redis node节点上,然后再将其删除,如果一个Redis node节点上的槽位没有被完全迁移,删除该node的时候会提示有数据且无法删除。
bash
# 查看要被迁移的节点上有多少插槽
[root@master1 ~]# redis-cli --cluster check 192.168.83.20:6379
192.168.83.20:6381 (49402647...) -> 0 keys | 200 slots | 1 slaves. # 分配200个插槽
# 查看到6381上面的插槽编号,计算出数量
M: 49402647b0a7a55d0b00b187cf56befdd16c56c7 192.168.83.20:6381
slots:[5461-5660] (200 slots) master
1 additional replica(s)
[root@master1 ~]# redis-cli --cluster reshard 192.168.83.20:6379 --cluster-from 49402647b0a7a55d0b00b187cf56befdd16c56c7 --cluster-to 72fdbc7928adc57673010ebf68f204f98a641646 --cluster-slots 100
Do you want to proceed with the proposed reshard plan (yes/no)? yes #此处输入yes
删除节点的语法格式为:
bash
redis-cli --cluster del-node host:port node_id
del-node命令用于从集群中删除节点,参数说明如下:
- host:集群中已经存在的主机地址
- port:集群中已经存在的主机对应的端口号
- node_id:要删除的节点ID
bash
# 先删除从节点,如果先删除主节点,从会故障转移
[root@master1 ~]# redis-cli --cluster del-node 192.168.168.20:6379 db877460361b3755502fa8f0cea523b99e9da032
[root@master1 ~]# redis-cli --cluster del-node 192.168.168.20:6379 3fb85117061c248a338ac7dfc3b460bc31e1a6d2