当前位置: 首页 > ds >正文

【Redis】集群

文章目录

  • 1. 集群
  • 2. 数据分片(重点)
    • 2.1 哈希求余
    • 2.2 一致性哈希算法
    • 2.3 哈希槽分区算法(Redis使用)
  • 3. 集群搭建(基于docker)
    • 3.1 搭建
    • 3.2 故障处理
    • 3.3 集群扩容

在这里插入图片描述

1. 集群

哨兵模式提⾼了系统的可⽤性,但是真正⽤来存储数据的还是master和slave节点,所有的数据都需要存储在单个master和slave节点中;如果数据量很⼤,接近超出了master/slave所在机器的物理内存,就可能出现严重问题了

如何获取更⼤的空间?加机器即可!所谓"⼤数据"的核⼼,其实就是⼀台机器搞不定了,⽤多台机器来搞。

广义的集群:只要是多个机器,构成分布式系统,都可以认为是一个集群;前面说的主从结构、哨兵模式,也是可以称为广义的集群

Redis的集群就是在上述的思路之下,引⼊多组Master/Slave,每⼀组Master/Slave存储数据全集的⼀部分,从⽽构成⼀个更大的整体,称为Redis集群(Cluster).

在这里插入图片描述
每个Slave都是对应Master的备份(当Master挂了,对应的Slave会补位成Master)
每个红框部分都可以称为是⼀个分⽚(Sharding),如果全量数据进⼀步增加,只要再增加更多的分⽚即可解决

2. 数据分片(重点)

Redis cluster 的核⼼思路是⽤多组机器来存数据的每个部分,那么接下来的核⼼问题就是,给定⼀个数据(⼀个具体的key),那么这个数据应该存储在哪个分⽚上?读取的时候⼜应该去哪个分⽚读取?

2.1 哈希求余

跟哈希表类似,通过哈希函数映射到不同的位置
在这里插入图片描述
后续如果要取某个key的记录,也是针对key计算hash,再对N求余,就可以找到对应的分⽚编号了

  • 优点:简单⾼效,数据分配均匀
  • 缺点:⼀旦需要进⾏扩容,N改变了,原有的映射规则被破坏,就需要让节点之间的数据相互传输,重新排列,以满⾜新的映射规则,此时需要搬运的数据量是⽐较多的,开销较⼤.

2.2 一致性哈希算法

为了降低上述的搬运开销,能够更⾼效扩容,业界提出了"⼀致性哈希算法",key 映射到分⽚序号的过程不再是简单求余了,是改成以下过程:

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

在这里插入图片描述
这就相当于N个分⽚的位置,把整个圆环分成了N个管辖区间,Key的hash值落在某个区间内,就归对应区间管理
在这里插入图片描述

在这个情况下,如果扩容⼀个分片,如何处理呢?
原有分片在环上的位置不动,只要在环上新安排⼀个分片位置即可

在这里插入图片描述
此时,只需要把0号分片上的部分数据,搬运给3号分片即可,1号分片和2号分片管理的区间都是不变的

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

2.3 哈希槽分区算法(Redis使用)

为了解决上述问题(搬运成本高和数据分配不均匀),Rediscluster引入了哈希槽(hashslots)算法;这种算法,本质就是将上面的两种算法结合了一下。

hash_slot = crc16(key) % 16384
#crc16也是⼀种hash算法

相当于是把整个哈希值,映射到16384个槽位上,也就是[0,16383],然后再把这些槽位⽐较均匀的分配给每个分片

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

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

每个分片的节点使⽤位图来表⽰自己持有哪些槽位,对于16384个槽位来说,需要2048个字节(2KB)大小的内存空间表示

如果需要进⾏扩容,⽐如新增⼀个3号分片,就可以针对原有的槽位进⾏重新分配

⽐如可以把之前每个分⽚持有的槽位,各拿出⼀点,分给新分片
分⽚规则是很灵活的,每个分⽚持有的槽位也不⼀定连续

一种可能的分配⽅式:

  • 0号分片:[0,4095],共4096个槽位
  • 1号分片:[5462,9557],共4096个槽位
  • 2号分片:[10924,15019],共4096个槽位
  • 3号分片:[4096,5461]+[9558,10923]+[15020,16383],共4096个槽位

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

  1. Redis集群是最多有16384个分片吗?
  • Redis 集群并不是最多有 16384 个分片,而是将整个集群划分为16384 个哈希槽,每个槽位可以分配给不同的分片节点,16384个槽位是为了实现数据的均匀分布和高效的负载均衡,而不是指集群最多有 16384 个分片
  • 分片数量通常远小于槽位数量,Redis的作者建议集群分⽚数不应该超过1000
  1. 为什么是16384个槽位?
  • 节点之间通过⼼跳包通信,⼼跳包中包含了该节点持有哪些slots,这个是使⽤位图这样的数据结构表⽰的,表⽰16384个slots,需要的位图大小是2KB;如果给定的slots数更多了,⽐如65536个了,此时就需要消耗8KB空间位图表⽰了,对于内存来说不算什么,但是在频繁的⽹络⼼跳包中,还是⼀个不⼩的开销的.
  • 另⼀⽅⾯,Redis集群⼀般不建议超过1000个分⽚,所以16k对于最⼤1000个分⽚来说是⾜够⽤的,同时也会使对应的槽位配置位图体积不⾄于很⼤

3. 集群搭建(基于docker)

3.1 搭建

接下来基于docker,搭建⼀个集群,每个节点都是⼀个容器
在这里插入图片描述

  1. 创建⽬录和配置
redis-cluster/
├── docker-compose.yml 
└── generate.sh 

generate.sh内容如下

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
EOF
done# 注意cluster-announce-ip 的值有变化 
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
EOF
done

在这里插入图片描述

  1. 编写docker-compose.yml
version: '3.7'
networks:mynet:ipam:config:- subnet: 172.30.0.0/24services:redis1:image: 'redis:5.0.9'container_name: redis1restart: alwaysvolumes:- ./redis1/:/etc/redis/ports:- "6371:6379"- "16371:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.101redis2:image: 'redis:5.0.9'container_name: redis2restart: alwaysvolumes:- ./redis2/:/etc/redis/ports:- "6372:6379"- "16372:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.102redis3:image: 'redis:5.0.9'container_name: redis3restart: alwaysvolumes:- ./redis3/:/etc/redis/ports:- "6373:6379"- "16373:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.103redis4:image: 'redis:5.0.9'container_name: redis4restart: alwaysvolumes:- ./redis4/:/etc/redis/ports:- "6374:6379"- "16374:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.104redis5:image: 'redis:5.0.9'container_name: redis5restart: alwaysvolumes:- ./redis5/:/etc/redis/ports:- "6375:6379"- "16375:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.105redis6:image: 'redis:5.0.9'container_name: redis6restart: alwaysvolumes:- ./redis6/:/etc/redis/ports:- "6376:6379"- "16376:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.106redis7:image: 'redis:5.0.9'container_name: redis7restart: alwaysvolumes:- ./redis7/:/etc/redis/ports:- "6377:6379"- "16377:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.107redis8:image: 'redis:5.0.9'container_name: redis8restart: alwaysvolumes:- ./redis8/:/etc/redis/ports:- "6378:6379"- "16378:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.108redis9:image: 'redis:5.0.9'container_name: redis9restart: alwaysvolumes:- ./redis9/:/etc/redis/ports:- "6379:6379"- "16379:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.109redis10:image: 'redis:5.0.9'container_name: redis10restart: alwaysvolumes:- ./redis10/:/etc/redis/ports:- "6380:6379"- "16380:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.110redis11:image: 'redis:5.0.9'container_name: redis11restart: alwaysvolumes:- ./redis11/:/etc/redis/ports:- "6381:6379"- "16381:16379"command: redis-server /etc/redis/redis.confnetworks:mynet:ipv4_address: 172.30.0.111
  1. 启动容器:docker-compose up -d
  2. 构建集群,启动⼀个docker客⼾端
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. 使用集群

在这里插入图片描述

使用集群之后,之前学习过的命令,有些命令是同时操作多个key的,如果这些key不在同一个分片上,会出现问题
在这里插入图片描述

3.2 故障处理

如果集群中有节点挂了怎么办?
从节点挂了,无所谓;如果是主节点挂了呢?
主节点才能处理写操作,从节点是不能写的;当从节点写时,会自动重定向到主节点

在这里插入图片描述

当主节点A挂了,处理流程跟哨兵模式是类似的

会从其从节点中挑选一个从节点B,作为新的主节点B,剩余从节点的主节点变为节点B;当旧的主节点A恢复时,A就变为B的从节点。

具体处理流程:

故障判定

集群中的所有节点,都会周期性的使⽤⼼跳包进⾏通信.

  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节点都挂了.

故障迁移
上述例⼦中,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还会把自己成为主节点的消息,同步给其他集群的节点,⼤家也都会更新自己保存的集群结构信息.

3.3 集群扩容

扩容是⼀个在开发中⽐较常遇到的场景.
随着业务的发展,现有集群很可能⽆法容纳⽇益增⻓的数据,此时给集群中加⼊更多新的机器,就可以使存储的空间更⼤了.

  1. 把新的主节点加⼊到集群

上⾯已经把redis101-redis109重新构成了集群,接下来把redis110和redis111也加⼊集群,此处我们把redis110作为主机,redis111作为从机

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

add-node后的第⼀组地址是新节点的地址,第⼆组地址是集群中的任意节点地址

在这里插入图片描述

此时,我们新加入的master是没有分配槽的

  1. 重新分配slots
redis-cli --cluster reshard 172.30.0.101:6379

执行之后,会进⼊交互式操作,redis会提⽰用户输⼊以下内容:

  • 多少个slots要进⾏reshard
  • 哪个节点来接收这些slots?(填写这个节点的集群节点id)
  • 这些slots从哪些节点搬运过来?
    • all:表示从其他所有的节点都进行搬运
    • 手动指定,从某一个或几个节点搬运slots,以done结尾

输入all以后,会给出搬运计划,此时还未真实搬运
当程序员输入yes后,搬运开始;不仅仅是slots重新划分,也会把slots上对应的数据搬运到新主机上。

  1. 给新的主节点添加从节点

光有主节点了,此时扩容的⽬标已经初步达成,但是为了保证集群可⽤性,还需要给这个新的主节点添加从节点,保证该主节点宕机之后,有从节点能够顶上

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]

执⾏完毕后,从节点就已经被添加完成了

在这里插入图片描述

在这里插入图片描述

http://www.xdnf.cn/news/6331.html

相关文章:

  • 第29节:现代CNN架构-Inception系列模型
  • 智能视觉检测技术:制造业质量管控的“隐形守护者”
  • Gartner《分布式和微服务架构中数据架构》学习心得
  • 【Linux笔记】——Linux线程理解与分页存储的奥秘
  • UE5 像素推流
  • Java GUI开发全攻略:Swing、JavaFX与AWT
  • Kubernetes控制平面组件:Kubelet详解(四):gRPC 与 CRI gRPC实现
  • nginx定义error 403页面
  • Java—封装、继承与多态
  • TypeScript装饰器:从入门到精通
  • LangChain4j入门(一)SpringBoot整合并接入Deepseek
  • 前端~三维地图(cesium)动态材质飞线
  • nacos:服务无法注册到nacos服务中心
  • Linux 动静态库详解
  • JS 中 Object.keys() 和 Object.values() 的深度解析与应用
  • 蓝桥杯 17. 修改数组
  • 【Linux高级IO】多路转接之epoll
  • Linux——mysql主从复制与读写分离
  • 人工智能+ERP:政策新规下企业智能化转型路径
  • 【vue】axios网络请求介绍
  • 【2025版】Spring Boot面试题
  • C语言_自定义类型:结构体
  • (4)python开发经验
  • (十七)Java日期时间API全面解析:从传统Date到现代时间处理
  • Ros2 - Moveit2 - DeepGrasp(深度抓握)
  • golang -- 如何让main goroutine等一等
  • 数智驱动——AI:企业数字化转型的“超级引擎”
  • FreeRTOS学习笔记
  • 【Java学习笔记】finalize方法
  • 前后端分离博客 Weblog 项目实战