redis cluster 学习与梳理

参考:Redis Cluster 原理相关说明!!!

参考:Redis Cluster 集群一致性原理及slot迁移测试 !!!

参考:REDIS CLUSTER 搭建,扩容缩容基本原理!!!

参考:redis实战系列(16篇)

参考:Redis cluster集群模式的原理

参考:redis实战第十四篇 redis cluster ask重定向

参考:redis实战第十二篇 redis cluster请求重定向

参考:【RedisCluster】redis-cluster原理概述(虚拟槽+move/ask+cluster集群故障发现/恢复)

 

 客户端路由

moved重定向

1.每个节点通过通信都会共享Redis Cluster中槽和集群中对应节点的关系
2.客户端向Redis Cluster的任意节点发送命令,接收命令的节点会根据CRC16规则进行hash运算与16383取余,计算自己的槽和对应节点
3.如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端
4.如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常
5.客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息
6.客户端向目标节点发送命令,获取命令执行结果

在集群模式下,redis在接收到键任何命令时会先计算该键所在的槽,如果改键所在的槽位于当前节点,则直接执行命令,如果改键位于其它节点,则不执行该命令,返回重定向信息。 比如hello这个键槽866上,而曹866位于31节点上,假设在32上执行get hello ,则会返回重定向信息。

127.0.0.1:6380> get hello
(error) MOVED 866 192.168.0.31:6380

ask重定向

在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移

当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的节点信息

如果此时正在进行集群扩展或者缩空操作,当客户端向正确的节点发送命令时,槽及槽中数据已经被迁移到别的节点了,就会返回ask,这就是ask重定向机制

1.客户端向目标节点发送命令,目标节点中的槽已经迁移支别的节点上了,此时目标节点会返回ask转向给客户端
2.客户端向新的节点发送Asking命令给新的节点,然后再次向新节点发送命令
3.新节点执行命令,把命令执行结果返回给客户端

 

moved异常与ask异常的相同点和不同点:

两者都是客户端重定向
moved异常:槽已经确定迁移,即槽已经不在当前节点
ask异常:槽还在迁移中

 

通过cluster keyslot [key]可以计算出key对用的槽。
通过./redis-cli -c -p [port]可以以集群模式访问,它会自动的帮助我们捕获moved异常,自动跳转到新的节点并执行命令。

redis-cli 原始方式连接 

[root@iz2zechwdfwvcm1rr3upjvz redis]# ./redis-cli -p 8000
127.0.0.1:8000> set zhangsan 100
# 返回moved异常,需要转到8002节点执行
(error) MOVED 12767 127.0.0.1:8002
# 退出
127.0.0.1:8000> exit
#手动连接8002节点执行set命令
[root@iz2zechwdfwvcm1rr3upjvz redis]# ./redis-cli -p 8002
127.0.0.1:8002> set zhangsan 100
OK

 

redis-cli 集群模式连接 

[root@iz2zechwdfwvcm1rr3upjvz redis]# ./redis-cli -c -p 8000
#发送set命令
127.0.0.1:8000> set zhangsan 100
# 跳转到8002节点,并执行set命令
-> Redirected to slot [12767] located at 127.0.0.1:8002
OK
127.0.0.1:8002> 

 

 

多节点命令实现

1.串行mget

定义for循环,遍历所有的key,分别去所有的Redis节点中获取值并进行汇总,简单,但是效率不高,需要n次网络时间

2.串行IO

对串行mget进行优化,在客户端本地做内聚,对每个key进行CRC16hash,然后与16383取余,就可以知道哪个key对应的是哪个槽

本地已经缓存了槽与节点的对应关系,然后对key按节点进行分组,成立子集,然后使用pipeline把命令发送到对应的node,需要nodes次网络时间,大大减少了网络时间开销

3.并行IO

并行IO是对串行IO的一个优化,把key分组之后,根据节点数量启动对应的线程数,根据多线程模式并行向node节点请求数据,只需要1次网络时间

4.hash_tag

将key进行hash_tag的包装,然后把tag用大括号括起来,保证所有的key只向一个node请求数据,这样执行类似mget命令只需要去一个节点获取数据即可,效率更高


Redis Cluster 集群一致性原理及slot迁移测试

集群信息一致性问题

主从和slot的一致性是由epoch来管理的. epoch就像Raft中的term, 但仅仅是像. 每个节点有一个自己独特的epoch和整个集群的epoch, 为简化下面都称为node epoch和cluster epoch. node epoch一直递增, 其表示某节点最后一次变成主节点或获取新slot所有权的逻辑时间. cluster epoch则是整个集群中最大的那个node epoch. 我们称递增node epoch为bump epoch, 它会用当前的cluster epoch加一来更新自己的node epoch.

在使用gossip协议中, 如果多个节点声称不同的集群信息, 那对于某个节点来说究竟要相信谁呢? Redis Cluster规定了每个主节点的epoch都不可以相同. 而一个节点只会去相信拥有更大node epoch的节点声称的信息, 因为更大的epoch代表更新的集群信息.
原则上:
(1)如果epoch不变, 集群就不应该有变更(包括选举和迁移槽位)
(2)每个节点的node epoch都是独一无二的
(3)拥有越高epoch的节点, 集群信息越新

 

集群所有槽的指派信息

通过将所有槽的指派信息保存在clusterState.slots数组里面,程序要检查槽i是否已经被指派,又或者取得负责处理槽i的节点,只需要访问clusterState.slots[i]的值即可,复杂度仅为O(1)。  (PS:这里有个问题,clusterState是前面的那个结构吗,另外这是个二进制数组,怎么处理槽i的节点?)

 

请求重定向

由于每个节点只负责部分slot,以及slot可能从一个节点迁移到另一节点,造成客户端有可能会向错误的节点发起请求。因此需要有一种机制来对其进行发现和修正,这就是请求重定向。有两种不同的重定向场景:

 a) MOVED错误

  • 请求的key对应的槽不在该节点上,节点将查看自身内部所保存的哈希槽到节点 ID 的映射记录,节点回复一个 MOVED 错误。

  • 需要客户端进行再次重试。

 b) ASK错误

  • 请求的key对应的槽目前的状态属于MIGRATING状态并且当前节点找不到这个key了,节点回复ASK错误。ASK会把对应槽的IMPORTING节点返回给你,告诉你去IMPORTING的节点查找。

  • 客户端进行重试 首先发送ASKING命令,节点将为客户端设置一个一次性的标志(flag),使得客户端可以执行一次针对 IMPORTING 状态的槽的命令请求,然后再发送真正的命令请求。

  • 不必更新客户端所记录的槽至节点的映射。

 

四、数据迁移

当槽x从Node A向Node B迁移时,Node A和Node B都会有这个槽x,Node A上槽x的状态设置为MIGRATING,Node B上槽x的状态被设置为IMPORTING。

MIGRATING状态

  1. 如果key存在则成功处理

  2. 如果key不存在,则返回客户端ASK,客户端根据ASK首先发送ASKING命令到目标节点,然后发送请求的命令到目标节点

  3. 当key包含多个:

    1. 如果都存在则成功处理

    2. 如果都不存在,则返回客户端ASK

    3. 如果一部分存在,则返回客户端TRYAGAIN,通知客户端稍后重试,这样当所有的key都迁移完毕的时候客户端重试请求的时候回得到ASK,然后经过一次重定向就可以获取这批键

  4. 此时不刷新客户端中node的映射关系

IMPORTING状态

  1. 如果key不在该节点上,会被MOVED重定向,刷新客户端中node的映射关系

  2. 如果是ASKING则命令会被执行,key不在迁移的节点已经被迁移到目标的节点

  3. Key不存在则新建

 

Key迁移的命令:

1 DUMP:在源(migrate)上执行
2 RESTORE:在目标(importing)上执行
3 DEL:在源(migrate)上执行 

经过上面三步可以将键迁移,然后再将处于MIGRATING和IMPORTING状态的槽变为常态,完成整个重新分片的过程,具体的信息可见Redis Cluster部署、管理和测试 。

4.1 读写请求

槽里面的key还未迁移,并且槽属于迁移中。

假如槽x在Node A,需要迁移到Node B上,槽x的状态为migrating,其中的key1还没轮到迁移。此时访问key1则先计算key1所在的Slot,存在key1则直接返回。

4.2 MOVED请求

槽里面的key已经迁移过去,并且槽属于迁移完。

假如槽x在Node A,需要迁移到Node B上,迁移完成。此时访问key1则先计算key1所在的Slot,因为已经迁移至Node B上,Node A上不存在,则返回 moved slotid IP:PORT,再根据返回的信息去Node B访问key1,此时更新slot和node的映射。

4.3 ASK请求

槽里面的key已经迁移完,并且槽属于迁移中的状态。

假如槽x在Node A,需要迁移到Node B上,迁移完成,但槽x的状态为migrating。此时访问key1则先计算key1所在的Slot,不存在key1则返回ask slotid IP:PORT,再根据ask返回的信息发送asking请求到Node B,没问题后则最后再去Node B上访问key1,此时不更新slot和node的映射。

 

 


 Redis cluster集群模式的原理

参考:中华石杉Java工程师面试突击

节点间的内部通信机制

 

1、基础通信原理
(1)redis cluster节点间采取gossip协议进行通信  跟集中式不同,不是将集群元数据(节点信息,故障,等等)集中存储在某个节点上,而是互相之间不断通信,保持整个集群所有节点的数据是完整的  集中式:好处在于,元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候立即就可以感知到; 不好在于,所有的元数据的跟新压力全部集中在一个地方,可能会导致元数据的存储有压力  gossip:好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力; 缺点,元数据更新有延时,可能导致集群的一些操作会有一些滞后。
 
(2)10000端口  每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口  每隔节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong。
 
(3)交换的信息  故障信息,节点的增加和移除,hash slot信息,等等
 
2、gossip协议  
gossip协议包含多种消息,包括ping,pong,meet,fail,等等  
  • meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信  
  • redis-trib.rb add-node  其实内部就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群  
  • ping: 每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据  
    • 每个节点每秒都会频繁发送ping给其他的集群,ping,频繁的互相之间交换数据,互相进行元数据的更新  
  • pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新  
  • fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了
 
3、ping消息深入  
ping很频繁,而且要携带一些元数据,所以可能会加重网络负担  
每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点  
当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了  
比如说,两个节点之间都10分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题  
所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送的频率  
每次ping,一个是带上自己节点的信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换  
至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息

参考:cluster setslot指令

cluster setslot

CLUSTER SETSLOT slot IMPORTING|MIGRATING|STABLE|NODE [node-id]
自3.0.0起可用。
时间复杂度: O(1)

CLUSTER SETSLOT 负责以不同方式更改接收节点中散列槽的状态。它可以取决于所使用的子命令:

1. MIGRATING子命令:设置迁移状态下的散列槽。

2. IMPORTING子命令:在导入状态下设置哈希槽。

3. STABLE 子命令:清除散列槽中的任何导入/迁移状态。

4. NODE 子命令:将哈希槽绑定到不同的节点。

该命令及其子命令集对于启动和结束集群实时重新分片操作很有用,它通过在源节点中设置迁移状态的哈希槽以及在目标节点中导入状态来完成。

下面介绍了每个子命令。最后你会找到如何使用这个命令和其他相关命令执行实时重新分片的描述。


 

REDIS CLUSTER 搭建,扩容缩容基本原理

## step 3 扩容

## 扩容,给新的一个master 节点分配槽

## 多个来源节点的情况

## 遍历每个节点,计算每个节点应该移出多少个槽 (numslots.to_f/source_tot_slots*s.slots.length)

## numslots.to_f 总共要移动的槽数

## source_tot_slots 所有来源节点的槽数

## s.slots.length 单个来源节点拥有的槽数

## 循环每个slot:

## 接收节点执行 cluster setslot <slot> importing <node_id>   (node_id为源节点id)

## 来源节点执行 cluster setslot <slot> migrating <node_id>    (node_id为接收节点id)

## 源节点执行 cluster countkeysinslot <slot> 查看要移动的槽 有没有键

## 如果有 则 继续执行 cluster getkeysinslot <slot> <count>  查看slot节点的多少个key值, 如果上一步槽的键数量为空跳过此步骤

## 然后执行 MIGRATE 源节点ip 源节点port "" 0 5000 KEYS key1 key2 key3  把键先迁移,执行这条命令后该键不可读不可写

## 分别在源节点和接收节点执行 cluster setslot <slot> node <node_id>   

## 现在看 主要在接收节点上执行 cluster setslot ...node ...命令,只有在接收节点上面执行后会跟新接收节点的epoch 版本号,然后发消息跟其他节点说该slot已经归我管了,如果epoch比接收信息的节点高,接收信息的节点会跟新该slot的状态

## 而源节点上面执行cluster setslot .... node ...命令只移除mirating flag 并不会更新版本号

## 那么问题来了,那importing 和migrating 步骤不就可以省略吗,那源节点也没必要执行cluster setslot ... node ...命令了(其实并不是这样,详见测试)

 

posted @ 2021-07-31 23:47  细雨骑驴入剑门  阅读(131)  评论(0编辑  收藏  举报