欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > Redis的集群

Redis的集群

2025/4/16 14:51:23 来源:https://blog.csdn.net/wk200411/article/details/147125542  浏览:    关键词:Redis的集群

Redis的集群

  • 一.集群简介
  • 二.数据分片
    • 1.哈希求余
    • 2.哈希求余存在的问题
    • 3.一致性哈希算法
    • 4.哈希槽分区算法(redis采用的方法)
      • 集群的片数最多是多少?
      • 槽位为什么是16384
  • 三.搭建集群
    • 1.创建目录和配置
    • 2.创建redis容器
  • 四.使用集群
    • 1.使用集群的方法
    • 2.使用集群故障分析
      • 故障处理流程
        • 1).故障判定:
        • 2).故障迁移(Raft算法)
  • 五.集群扩容
    • 1.将新的主节点加入集群
    • 2.重新分配slots
      • 3.从节点添加到集群中

一.集群简介

广义的集群指的是,只要你是多个机器,构成了分布式系统,都可以称为是一个集群,比如果前面的主从结构,哨兵模式,都可以称为广义的集群。

狭义的集群指的是redis提供的一种集群的工作模式,在这种集群模式下,主要是为了解决存储空间不足的问题(拓展存储空间)

二.数据分片

因为redis存储数据是通过内存的形式来存储,那么数据比较大时,就没办法通过一个redis服务器来进行存储,此时就可以使用集群的方式来进行拓展存储空间,也就是使用多台机器的方式来分担这个数据。

但是,如何进行合理的分配数据才能让redis更高效呢,此时就会需要使用到一些数据分片的算法。

1.哈希求余

哈希求余是借鉴了哈希表的思想,将key通过哈希函数函数获取到一个整数值(哈希值),再将哈希值进行取模操作,一般是模上数组的长度,此时就会得到对应的数组下标,才这个key存储到这个下表中即可。

将此思想用到数据分片中,比如现在有一些分片,此时就可以针对要存储数据的分片的key(redis都是键值对存储的数据结构),将其通过哈希函数(可以使用MD5),算出它的哈希值,在通过哈希值 %分片个数(N)就能获取到这个key存储的下标了。

后续查询key的时候也是使用同样的算法进行,key是一样的,哈希函数也是一样的,那么得到的分片肯定也是一样的。

2.哈希求余存在的问题

数据搞成分片的形式就是为了提高存储能力,但是分片越多,能存储的数据确实也越多,但是成本会更高了,也就是集群需要扩容了。

一般分片都是搞3个,如果业务数据多了,3个分片肯定是不够的,此时就需要扩容,引入新的分片,但是引入了新的分片此时N的个数就变了,此时在通过哈希值 % 分片个数就不是原来的下标了,就需要重新进行分配(搬运数据)

分片中的数据都是通过哈希函数计算过的哈希值(100~120,随便给的,方便叙述)(下图是扩容前的分片结构):
在这里插入图片描述

扩展后的数据分片,20个数据搬运了17个数据,如果数据更多呢,那岂不是开销更大了:
在这里插入图片描述

3.一致性哈希算法

  1. 把0->2^32-1这个数据空间,映射到⼀个圆环上。数据按照顺时针方向增长。
    在这里插入图片描述
  2. 假设当前存在三个分片,就把分片放到圆环的某个位置上。在这里插入图片描述
  3. 假定有⼀个key,计算得到hash值为H,那么这个key映射到哪个分片呢?规则很简单,就是从H所在位置,顺时针往下找,找到的第⼀个分片,即为该key所从属的分片。

在这里插入图片描述

这就相当于,N个分片的位置,把整个圆环分成了N个管辖区间。Key的hash值落在某个区间内,就归对应区间管理。
在这里插入图片描述
进行扩容的时候,原有分片在环上的位置不动,只要在环上新安排⼀个分片位置即可。在这里插入图片描述
此时,只需要把0号分片上的部分数据,搬运给3号分片即可。1号分片和2号分片管理的区间都是不变
的。

优点:大大降低了扩容时数据搬运的规模,提高了扩容操作的效率。
缺点:数据分配不均匀(有的多有的少,数据倾斜)

4.哈希槽分区算法(redis采用的方法)

为了解决上述问题(搬运成本高和数据分配不均匀),Redis cluster引入了哈希槽(hash slots)算法:hash_slot = crc16(key) % 16384,hash_slot是哈希槽,crc是hash值的算法,16384是固定的数值。

相当于是把整个哈希值,映射到16384个槽位上,也就是[0,16383]。然后再把这些槽位比较均匀的分配给每个分片。每个分片的节点都需要记录自己持有哪些分片

假设当前有三个分片,⼀种可能的分配方式:

  • 0号分片:[0,5461],共5462个槽位
  • 1号分片:[5462,10923],共5462个槽位
  • 2号分片:[10924,16383],共5460个槽位

如果需要进行扩容,比如新增⼀个3号分片,就可以针对原有的槽位进行重新分配。比如可以把之前每个分片持有的槽位,各拿出⼀点,分给新分片一种可能的分配方式:
• 0号分[0,4095],共4096个槽位
• 1号分片:[5462,9557],共4096个槽位
• 2号分片:[10924,15019],共4096个槽
• 3号分片:[4096,5461]+[9558,10923]+[15019,16383],共4096个槽位

我们在实际使用Redis集群分片的时候,不需要手动指定哪些槽位分配给某个分片,只需要告诉某个分片应该持有多少个槽位即可,Redis会自动完成后续的槽位分配,以及对应的key搬运的工作。

集群的片数最多是多少?

需要注意的是,redis集群的分片不应该超过1000个,更不可能有16384个分片,首先集群的数据均匀都难以保证,因为会导致有些槽位有多个key而有些槽位没有key的情况。其次,如果真的有1.6w个分片,此时就需要几万台主机来构成集群,那么集群的可用性可想而知是能有多差了。

槽位为什么是16384

节点之间通过心跳包通信,心跳包中包含了该节点持有哪些slots。这个是使用位图这样的数据结构表示的表示16384(16k)个slots,需要的位图大小是2KB。

如果给定的slots数更多了,比如65536个了,此时就需要消耗更多的空间,8KB位图表示了。8KB,对于内存来说不算什么,但是在频繁的网络心跳包中,还是⼀个不小的开销的。

另一方面,Redis集群⼀般不建议超过1000个分片,所以16k对于最大1000个分片来说是足够用的,同时也会使对应的槽位配置位图体积不至于很大。

三.搭建集群

1.创建目录和配置

(1) 现在之前创建哨兵的目录中进行创建一个新的目录:
在这里插入图片描述
(2) 在新的目录下创建两个文件:
在这里插入图片描述
(3)把之前启动的redis容器关闭:在这里插入图片描述

(4)generate.sh脚本文件的编写来创建11个redis节点:

for循环类似于Java中的for each;区间通过Linux的命令seq来生成指定闭区间的数值;shell中不通过{}来表示代码块,而是表示变量,而代码块通过do和done的方式来表示;\是续行符:把下一行和当前行的内容进行合并,shell默然情况下,要求把所有代码都写到一行,所以就可以通过续行符来美化代码。

循环体中会通过$获取port的值来进行创建对应标号的redis目录,并且shell中的创建字符串不使用+号进行拼接,直接写就行了。

cluster-enabled yes:开启集群
cluster-config-file nodes.conf:集群的相关配置信息,不需要手动开启redis自动生成。
cluster-node-timeout:心跳包超时时间
cluster-announce-ip:该redis节点所在的主机IP,当前使用的是docker容器模拟的主机,此处写的应该是docker容器的IP。
cluster-announce-port:port就是redis节点自身绑定的端口(容器内的端口),不同的容器内部可以有相同的端口,后续进行端口映射,再把这些容器内的端口映射到容器外的不同端口即可。

redis的配置项的一些东西可以通过vim etc/redis/redis.conf通过查看配置文件就可以知道了。在这里插入图片描述
5.运行generate.sh的脚本文件:在这里插入图片描述

2.创建redis容器

(1)配置docker-compose.yml文件

在这里插入图片描述

version: '3.7'
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
- ./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

6.启动容器(启动之前,干掉之前启动的redis服务器)
在这里插入图片描述
在这里插入图片描述
7.构建集群

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 表示每个主节点需要两个从节点备份。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四.使用集群

1.使用集群的方法

不同的方式连接redis客户端,IP和端口号时不同的:
在这里插入图片描述
查看节点信息:

在这里插入图片描述

设置集群后,当前数据就分片了,key1这个key通过hash 计算之后,得到的slot 9189 属于102这个分片。所以操作的这个客户端就无法插入数据。但是可以在启动redis-cli时加上-c选项,此时客户端发现当前key的操作不在当前分片上,就能够自动的重定向到对应的分片主机。
在这里插入图片描述
在这里插入图片描述

2.使用集群故障分析

此时如果使用一些能够操作多个key的指令,这些key在不同的分片上的话,此时就会出现问题:在这里插入图片描述
但是可以通过一些特殊的方式来解决这个问题,也就是hash tag(要自己去搜一下)

集群的主节点挂了之后,会和之前哨兵一样,自动把挂了的主节点中的从节点中的任意一个作为新的主节点。

先关闭一个主节点redis1:在这里插入图片描述
在这里插入图片描述
此时redis1已经挂了,状态时fail。之前101是主节点,105和106是从节点,当101挂了之后,105成为了新的主节点,106成为了105的从节点。
在这里插入图片描述
重新启动101节点:
在这里插入图片描述
101就变成了从节点了:在这里插入图片描述

故障处理流程

1).故障判定:
  1. 节点A给节点B发送ping包,B就会给A返回⼀个pong包。ping和pong除了 message type属性之外,其他部分都是⼀样的。这里包含了集群的配置信息(该节点的id,该节点从属于哪个分片是主节点还是从节点,从属于谁,持有哪些slots的位图…).
  2. 每个节点,每秒钟,都会给⼀些随机的节点发起ping包,而不是全发⼀遍。这样设定是为了避免在节
    点很多的时候,心跳包也非常多(比如有9个节点,如果全发,就是9*8有72组心跳了,而且这是按照N^2这样的级别增长的)
  3. 当节点A给节点B发起ping包,B不能如期回应的时候,此时A就会尝试重置和B的tcp连接,看能
    否连接成功。如果仍然连接失败,A就会把B设为PFAIL状态(相当于主观下线).
  4. A判定B为PFAIL之后,会通过redis内置的Gossip协议,和其他节点进行沟通,向其他节点确认B
    的状态。(每个节点都会维护⼀个自己的"下线列表",由于视⻆不同,每个节点的下线列表也不⼀定相
    同).
  5. 此时A发现其他很多节点,也认为B为PFAIL,并且数目超过总集群个数的⼀半,那么A就会把B标
    记成FAIL(相当于客观下线),并且把这个消息同步给其他节点(其他节点收到之后,也会把B标记成
    FAIL)⾄此,B就彻底被判定为故障节点了

某个或者某些节点宕机,有的时候会引起整个集群都宕机(称为fail状态).以下三种情况会出现集群宕机:
• 某个分片,所有的主节点和从节点都挂了.
• 某个分片,主节点挂了,但是没有从节点.
• 超过半数的master节点都挂了.

2).故障迁移(Raft算法)

上述例⼦中,B故障,并且A把BFAIL的消息告知集群中的其他节点.

  • 如果B是从节点,那么不需要进行故障迁移。
  • 如果B是主节点,那么就会由B的从节点(比如C和D)触发故障迁移了。

所谓故障迁移,就是指把从节点提拔成主节点,继续给整个redis集群提供支持。
具体流程如下:

  1. 从节点判定自己是否具有参选资格。如果从节点和主节点已经太久没通信(此时认为从节点的数据和
    主节点差异太大了),时间超过阈值,就失去竞选资。
  2. 具有资格的节点,比如C和D,就会先休眠⼀定时间。休眠时间=500ms基础时间+[0,500ms]随机
    时间+排名*1000ms。offset的值越大,则排名越靠前(越小)。
  3. ⽐如C的休眠时间到了,C就会给其他所有集群中的节点,进行拉票操作。但是只有主节点才有投票资格。
  4. 主节点就会把自己的票投给C(每个主节点只有1票)。当C收到的票数超过主节点数目的⼀半,C就
    会晋升成主节点。(C自己负责执行slaveof no one,并且让D执行slaveof C)。
  5. 同时,C还会把自己成为主节点的消息,同步给其他集群的节点。大家也都会更新自己保存的集群结构信息。

五.集群扩容

集群扩容操作是一件风险较高,成本较大的操作!

此处101-109 有9个主机,构成了3主,6从结构的集群,此时110和111也加入到急群众,以110为master,111为slave把数据分片从3变成4来进行操作。

1.将新的主节点加入集群

将新的主节点加入到集群中的命令:

redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379

在这里插入图片描述
在这里插入图片描述
此时只是将110这个新的主节点加入到集群中而已,并没有槽位。
在这里插入图片描述

2.重新分配slots

执行重新分配slots的命令:

redis-cli --cluster reshard 172.30.0.101:6379

在这里插入图片描述
在这里插入图片描述
此时移动1/4个分片(4096)即可:

在这里插入图片描述
接收分片的节点的ID(110那个主节点的ID):在这里插入图片描述
如何移动slots

  1. all,表示从其他每个持有slots的master都拿过来
  2. 手动指定,从某一个或者某几个节点来移动slots(以done为结尾)

此处使用的是all:
在这里插入图片描述

询问是否真正搬运,是的话输入yes,此时不只有slots进行划分,也会把slots上对应的数据也搬运到新的主机上:
在这里插入图片描述

此时每个主节点都有新的slots:在这里插入图片描述

搬运过程的访问:

在搬运slots/key的过程中,客户端可以访问集群,但是只能访问为搬运的key,正在搬运的key是没办法进行访问的。

3.从节点添加到集群中

从节点添加到集群中的命令:

redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id [172.30.1.110 节点的 nodeId(110的ID可以通过cluster nodes中获取,但是需要先进入redis的客户端)]

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词