31、Redis 7系列:集群(cluster)
一、官网
官网地址: Redis cluster specification | Redis
二、介绍
由于数据量过大,单个 Master
复制集难以承担,因此需要对 多个复制集 进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部分,这就是 Redis
的集群,其作用是提供在多个 Redis
节点间共享数据的程序集。
三、作用
1、Redis集群的特点
Redis
集群支持多个Master
,每个Master
下可以挂在多个Slave
。Cluster
自带Sentinel
的故障转移机制,内置了高可用,无需单独使用哨兵的功能。- 客户端与
Redis
节点的连接,不需要连接集群中的所有节点,只需要连接集群中任意可用节点即可。 槽位slot
负责分配各个物理服务节点。由对应的集群来负责维护 节点 、插槽 和 数据 之间的关系。
2、集群算法-分片-槽位slot
(1)槽位slot
**`Redis集群`** 没有采用一致性 **`hash`**,而是采用了 **哈希槽** 的的概念。
**`Redis集群`** 一共有 **16384** 个哈希槽,每个 **`key`** 通过 **`CRC16`** 算法后,对 **16384** 取模来决定放置哪个槽。集群中每个 **主节点** 负责一部分哈希槽。
举例: 当前集群由3个主节点
(2)分片
定义: 使用 Redis集群
时我们会将存储的数据分散到多台 Redis
机器(Master
)上,这称为 分片 。简言之,集群中的每个 Redis
实例(Master
)都被认为是整个数据的一个分片。
为了找到指定 key
的分片,我们对 key
进行 CRC16(key)
算法处理并通过对总分片数量(16383
)取模。然后,使用确定性哈希函数,这意味着指定的 key
将多次始终映射到同一个 分片 ,我们可以推断将来读取特定 key
的位置。
(3)槽位和分片的优点
方便增加和删除 Redis
主节点。
例如: 增加D主节点,需要将A、B、C中部分槽位分给D。
例如: 删除A主节点,将A的所有槽位分给B、C。然后将没有任何槽位的A节点在集群中删除即可。
由于从一个主节点将哈希槽移动到另一个主节点,并不会停止服务。所以,无论增加删除或者改变某个主节点的哈希槽的数量并不会对集群造成不可用状态。
(4)槽位映射-3种方案
-
哈希取模分区
用户每次读写操作都是根据公式:hash(key) % n
个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。
👍优点:
简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。
👎缺点:
原来规划好的节点,进行扩容或者缩容就比较麻烦。不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题。如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3
会变成Hash(key) /?
。此时经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。
某个Redis
机器宕机了,由于台数数量变化,会导致hash
取余全部数据重新洗牌。 -
一致性哈希算法分区
请查看如下文章:33、一致性哈希算法分区 -
哈希槽分区
哈希槽: 实质就是一个数组,数组[0,2^14 -1]
形成Hash slot
空间。
作用: 解决 均匀分配 的问题,在 数据和节点之间 又加入了一层,把这层称为 哈希槽(slot) 。用于 管理数据和节点之间的关系,现在就相当于节点前放的是槽,槽里放的是数据。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。
哈希解决的是映射问题,使用 key的哈希值来计算所在的槽 ,便于数据分配。
一个集群只能有 16384
个槽,编号:0 ~ 16383(0 ~ 2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。
集群会记录节点和槽的对应关系.解决了节点和槽的关系后,接下来就需要 对key求哈希值 ,然后对 16384
取模。余数是几,key
就落入其对应的槽里。HASH_SLOT = CRC16(key) mod 16384
。
以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
(5)为什么Redis集群的最大槽数是16384个?
Redis集群 并没有使用一致性哈希而是引入了哈希槽的概念。Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
但为什么哈希槽的数量是16384(2^14)个呢?
CRC16
算法产生的Hash值有16bit,该算法可以产生 2^16=65536 个值。
换句话说值是分布在 0~65535 之间,有更大的 65536 不用为什么只用16384就够呢?
作者在做mod运算的时候,为什么不mod65536,而选择mod16384? HASH_SLOT = CRC16(key) mod 65536
为什么没启用?
作者的主页:https://github.com/redis/redis/issues/2576
英译汉:
1、正常的心跳数据包带有节点的完整配置,可以用幂等方式用旧的节点替换旧节点,以便更新旧的配置。这意味着它们包含原始节点的插槽配置,该节点使用2k的空间和16k的插槽,但是会使用8k的空间(使用65k的插槽)。
2、同时,由于设计折衷,Redis集群不太可能扩展到1000个以上的主节点。
因此16k处于正确的范围内,以确保每个主机具有足够的插槽,最多可容纳1000个矩阵,但数量足够少,可以轻松地将插槽配置作为原始位图传播。
请注意,在小型群集中,位图将难以压缩,因为当N较小时,位图将设置的slot / N位占设置位的很大百分比。
📣通俗易懂的解释:
(1)如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
在消息头中最占空间的是 myslots[CLUSTER_SLOTS/8]
。
当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb。
当槽位为16384时,这块的大小是: 16384÷8÷1024=2kb。
拓展: 1kb(千字节) = 1024b(字节) = 8192bit(比特)
因为每秒钟,Redis节点需要发送一定数量的 ping
消息作为心跳包,如果槽位为65536,这个 ping
消息的消息头太大了,浪费带宽。
(2)Redis的集群主节点数量基本不可能超过1000个。
集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此Redis作者不建议Redis Cluster
节点数量超过1000个 。 那么,对于节点数在1000以内的 Redis Cluster
集群,16384个槽位够用了。没有必要拓展到65536个。
(3)槽位越小,节点少的情况下,压缩比高,容易传输
Redis主节点的配置信息中它所负责的哈希槽是通过一张 bitmap
(位图) 的形式来保存的,在传输过程中会对 bitmap
进行压缩,但是如果 bitmap
的填充率 slots/N
很高的话(N表示节点数), bitmap
的压缩率就很低。 如果节点数很少,而哈希槽数量很多的话,bitmap
的压缩率就很低。
(6)总结
Redis集群不保证强一致性,这意味着在特定的条件下,Redis集群会丢失一些系统收到的写入请求命令。
四、案例演示
1、3主3从Redis集群环境搭建
由于电脑硬件条件有限,本次案例搭建到3台服务器上。每台服务器分别是1主1从。
(1)修改配置文件
配置文件内容模板如下:
根据 IP 和 端口 的不同,分给新建配置文件,总共 6 个文件。
# 服务监听地址,用于客户端连接。通常默认为本机。
bind 0.0.0.0
# 是否支持后台运行
daemonize yes
# 是否开启安全保护模式
protected-mode no
# 端口
port 6381
# 日志文件路径
logfile "/home/ulanhada/customConfig/redis/cluster/cluster6381.log"
# pid文件路径
pidfile "/home/ulanhada/customConfig/redis/cluster/cluster6381.pid"
# 工作目录
dir "/home/ulanhada/customConfig/redis/cluster"
# RDB文件名称
dbfilename "dump6381.rdb"
# 开启AOF持久化
appendonly yes
# AOF文件名称
appendfilename "appendonly6381.aof"
# Redis密码
requirepass 123456
# master的Redis密码
masterauth 123456
# 开启Redis集群
cluster-enabled yes
# Redis集群的配置文件名称
cluster-config-file "nodes-6381.conf"
# Redis集群节点超时时限
cluster-node-timeout 15000
-
IP: 192.168.250.130
端口: 6381(master)、6382(slave)
新建配置文件: redisCluster6381.conf、redisCluster6381.conf
配置文件内容根据模板酌情修改 -
IP:192.168.250.131
端口: 6383(master)、6384(slave)
新建配置文件: redisCluster6383.conf、redisCluster6384.conf
配置文件内容根据模板酌情修改 -
IP:192.168.250.132
端口: 6385(master)、6386(slave)
新建配置文件: redisCluster6385.conf、redisCluster6386.conf
配置文件内容根据模板酌情修改
(2)检查必要端口
一定要先检查三台服务器的端口是否开放。
# 1、查看系统所有开放的端口
firewall-cmd --zone=public --list-ports
需要开放的端口包括: 客户端端口、集群总线端口
每个 Redis
集群中的节点都需要打开 两个TCP连接 。第一个连接用于正常的给 Client
提供服务,比如 6379
。第二个端口(通过在第一端口号上加10000)作为 数据端口,比如 16379
。第二个端口用于 集群总线 。这是一个用二进制协议的点对点通信信道。这个集群总线(Cluster bus
)用于节点的失败侦测、配置更新、故障转移授权等等。
则此案例需要开通的端口(在对应的服务器)如下:
客户端接口: 6381,6382,6383,6384,6385,6386
集群总线端口: 16381,16382,16383,16384,16385,16386
(登录用户如果不是 root
,命令前加上 sudo
)
# IP: 192.168.250.130
firewall-cmd --zone=public --add-port=6381/tcp --permanent
firewall-cmd --zone=public --add-port=6382/tcp --permanent
firewall-cmd --zone=public --add-port=16381/tcp --permanent
firewall-cmd --zone=public --add-port=16382/tcp --permanent
firewall-cmd --reload
# IP: 192.168.250.131
firewall-cmd --zone=public --add-port=6383/tcp --permanent
firewall-cmd --zone=public --add-port=6384/tcp --permanent
firewall-cmd --zone=public --add-port=16383/tcp --permanent
firewall-cmd --zone=public --add-port=16384/tcp --permanent
firewall-cmd --reload
# IP: 192.168.250.132
firewall-cmd --zone=public --add-port=6385/tcp --permanent
firewall-cmd --zone=public --add-port=6386/tcp --permanent
firewall-cmd --zone=public --add-port=16385/tcp --permanent
firewall-cmd --zone=public --add-port=16386/tcp --permanent
firewall-cmd --reload
(3)启动6台Redis主机实例
启动命令:
redis-server /home/ulanhada/customConfig/redis/cluster/redisCluster6381.conf
📣另外5台 Redis
实例启动命令根据实际IP和端口修改。
可以看到进程的后缀是以集群的方式启动。
(4)构建主从关系命令
# 地址根据实际情况修改
# --cluster-replicas 1: 表示为每个master创建一个slave节点
redis-cli -a 123456 --cluster create --cluster-replicas 1 192.168.250.130:6381 192.168.250.130:6382 192.168.250.131:6383 192.168.250.131:6384 192.168.250.132:6385 192.168.250.132:6386
理想中,我们觉得6381(master)-- 6382(slave),6383(master)-- 6384(slave),6385(master)-- 6386(slave)。根据下图发现并非如此。所以,我们要根据实际情况来定,以下图为准。
如果最下方看到如下图,说明集群关系 3主3从 完成~
(5)查看集群信息
redis-cli -a 123456 -p 6381
info replication
下图可以看到,各个 Redis
服务的实例ID、IP、端口、角色、slave的master实例ID等信息。
cluster nodes
2、3主3从Redis集群读写
(1)新增key值
发现在 set k1 v1
时会报错,然而 set k2 v2
就成功。
此时,我们应该注意 每次set的时候都会分配不同的槽位 。所以根据上图可知,set k1 v1
时,选则的槽位是 12706
。
根据上图可知 6381实例
的槽位范围是 [0~5406]
,所以汇报如上错误。所以,解决这个问题需要路由到具体的槽位才行。
(2)解决set报错
连接客户端命令结尾添加:-c
redis-cli -a 123456 -p 6381 -c
如上图所示,set k1 v1
成功。并且客户端 重定向 到 6385实例
上。简单的可以理解为,连接客户端添加 -c
后,会把整个 Redis
集群当作一个整体。我们无需关心具体怎么分配槽位,由 Redis
集群内部解决。
(3)查看某个key值的槽位
# 语法:cluster keyslot [key]
cluster keyslot k2
3、主从容错切换迁移
(1)容错切换迁移
查看集群信息,可知主从关系:
6381(master)~ 6384(slave)
6385(master)~ 6382(slave)
6383(master)~ 6386(slave)
模拟 6381(master)
宕机:
再次查看集群信息,发现主从关系变为:
6381(master)宕机
6384(master)上位成为master
6385(master)~ 6382(slave)
6383(master)~ 6386(slave)
6384(master) 可以正常使用,并且是 master
使角色
重新启动 6381实例
查看集群信息,发现主从关系变为:
6384(master)~ 6381(slave)
6385(master)~ 6382(slave)
6383(master)~ 6386(slave)
结合如上两图,可以发现 6381实例 以 slave
的角色回归。就像排队似的,半路走了再次回来就要重新排队。
(2)手动故障转移or节点从属调整
这个是强迫症患者的福音。 如果还想让 6381实例
当作master。则执行如下命令即可:(后台硬就是不一样,怎么折腾都可以是master)
cluster failover
4、主从扩容
(1)修改配置文件
再启动一台服务器(如果条件有限在原与服务器也可以),新建两个配置文件。
touch redisCluster6387.conf
touch redisCluster6388.conf
配置文件内容参考上文进行修改。
(2)检查端口
如果端口已开放,跳过该步骤~
开通的端口如下:
客户端接口: 6387,6388
集群总线端口: 16387,16388
(登录用户如果不是 root
,命令前加上 sudo
)
# IP: 192.168.250.133
firewall-cmd --zone=public --add-port=6387/tcp --permanent
firewall-cmd --zone=public --add-port=6388/tcp --permanent
firewall-cmd --zone=public --add-port=16387/tcp --permanent
firewall-cmd --zone=public --add-port=16388/tcp --permanent
firewall-cmd --reload
(3)启动Redis实例
redis-server redisCluster6387.conf
redis-server redisCluster6388.conf
如下两图可知,两个Redis实例启动后,角色都是master。
(4)将6387实例以master角色加入集群
# 语法:redis-cli -a 密码 --cluster add-node 实际IP地址:6387 实际IP地址:6381
redis-cli -a 123456 --cluster add-node 192.168.250.133:6387 192.168.250.130:6381
🎉添加成功~
(5)第一次检查集群信息
# 语法:redis-cli -a 密码 --cluster check 真实ip地址:6381
redis-cli -a 123456 --cluster check 192.168.250.130:6381
如下图发现,新增加的6387实例没有分配到槽位号。
(6)重新分配槽位号(reshard)
# 语法:redis-cli -a 密码 --cluster reshard IP地址:端口号
redis-cli -a 123456 --cluster reshard 192.168.250.130:6381
(7)第二次检查集群信息
6387实例已经分配的槽位,目前还是0个slave。
6387实例的槽位是3个新区间,之前master实例的槽位还是连续的。由于重新分配成本太高,所以前3家各自匀出来一部分,从6381/6383/6385三个旧节点分别匀出1364个槽位给新的6387实例。
(8)给6387实例分配slave
# 语法:redis-cli -a 密码 --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新master实例ID
redis-cli -a 123456 --cluster add-node 192.168.250.133:6388 192.168.250.133:6387 --cluster-slave --cluster-master-id fa57c339e1f980c23f97b81636115d818d602514
(9)第三次检查集群信息
5、主从缩容
本次演示:6387实例和6388实例下线。
(1)第一次检查集群信息
# 语法:redis-cli -a 密码 --cluster check 真实ip地址:6381
redis-cli -a 123456 --cluster check 192.168.250.130:6381
(2)在集群中删除6388实例
# 语法:redis-cli -a 密码 --cluster del-node ip:从机端口 从机6388实例ID
redis-cli -a 123456 --cluster del-node 192.168.250.133:6388 2075503f915f3dbd393be20453065ab7c442d62d
(3)第二次检查集群信息
根据下图可以看出,6487实例已经没有了slave。并且集群中 总实例数变成7个 。
(4)重更新分配槽位
将6387实例的槽位清空,重新分配。本次案例将槽位依次分配给其他三台master实例。
分给6381实例1365个槽位。
分给6383实例1366个槽位。
分给6385实例1635个槽位。
为了防止数据倾斜,建议大家还是平均分配。
(5)第三次检查集群信息
(6)删除6387实例
# 语法:redis-cli -a 密码 --cluster del-node ip:从机端口 6387实例ID
redis-cli -a 123456 --cluster del-node 192.168.250.133:6387 fa57c339e1f980c23f97b81636115d818d602514
(7)第四次检查集群信息
发现 6387实例 和 6388实例 已经彻底在集群中删除。
五、总结
1、集群中使用多键操作
由于 set
值的时候,不在同一个 slot槽位范围 下的键值无法使用 mset
、 mget
等多键操作。
可以通过**{}
**来定义 同一个组 的概念。使 key
中 {}
内相同内容的键值对放到一个 slot槽位范围去,对照下图类似 k1、k2、k3 都映射为 x
,自然 槽位范围 一样,即可以使用 mset
、 mget
等多键操作命令。
2、CRC16算法
Redis
集群有 16384
个哈希槽,每个 key
通过 CRC16算法
校验后对 16384
取模来决定放在具体的槽位。集群的每个节点(实例)负责一部分哈希槽。
3、常用命令
(1)配置文件参数:cluster-require-full-coverage
默认:yes
。 现在集群架构是3主3从,redis cluster
由3个 master
平分 16384
个 哈希槽(slot)
,每个 master
的小集群负责 1/3
的 哈希槽(slot)
,对应一部分数据。
cluster-require-full-coverage: 默认值 yes
, 即需要集群完整性,方可对外提供服 。通常情况,如果这3个小集群中,任何一个(1主1从)挂了,该集群对外可提供的数据只有 2/3
了, 整个集群是不完整的, Redis
默认在这种情况下,是不会对外提供服务的。
如果集群不完整也需要对外提供服务,需要将该参数设置为 no
。这样其中一个小集群挂掉,但是其他的小集群仍然可以对外提供服务。
(2)命令:CLUSTER COUNTKEYSINSLOT
查看该槽位编号有没有被占用。
# 语法:CLUSTER COUNTKEYSINSLOT [槽位编号]
CLUSTER COUNTKEYSINSLOT 449
0: 该槽位没有被占用
1: 该槽位已被占用
(3)命令:CLUSTER KEYSLOT
获取键位的槽位编号。
# 语法:CLUSTER KEYSLOT [key名称]
CLUSTER KEYSLOT k1
CLUSTER KEYSLOT k2
到这里 Redis 7系列:集群(cluster) 就结束了!!!🎉🎉🎉
欢迎小伙伴们学习和指正!!!😊😊😊
祝大家学习和工作一切顺利!!!😎😎😎