原来这就是Redis Cluster
作者:互联网
兄弟萌,想必大家学习 Redis 时,都听说过 Redis Cluster 吧,有没有想过这样一个问题,做 Redis 集群时,有了哨兵机制就能够实现监控以及自动故障转移,还要 Redis Cluster 干嘛呢?
下面,我们就深入聊一下 Redis Cluster,废话不多说,Let’s Go!
大家知道,Redis 有三种集群方式:主从复制,哨兵模式和集群 Cluster。
对于主从复制来说,如果 master 出现故障,不会自动恢复,需要人为干预来恢复。
对于哨兵模式来说,其实它就是主从复制的升级版,引入了 Sentinel(哨兵),哨兵的作用就是监控 Redis 系统的运行状况,它的功能包括:
- 监控主服务器和从服务器是否正常运行
- 主服务器出现故障时自动将从服务器转换为主服务器
但是,主从和哨兵存在难以扩容以及单机存储、读写能力受限的问题,并且集群之前都是一台 Redis 都是全量的数据,这样所有的 Redis 都冗余一份,就会大大消耗内存空间。
集群模式实现了 Redis 数据的分布式存储,集群通过分片(sharding)来进行数据共享,每个 Redis 节点存储不同的内容,并提供复制和故障转移功能。
1.分片
我们为了将不同的 key 分散放置到不同的 redis 节点,通常的做法是获取 key 的哈希值,然后根据节点数来求模,但这种做法有明显弊端,当我们需要增加或减少一个节点时,会造成大量的 key 无法命中,所以就提出了一致性 hash 的概念。
但是,Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念。
Redis Cluster 采用哈希槽分区,所有的键根据哈希函数映射到 0~16383 整数槽内,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,每一个节点负责维护一部分槽以及槽所映射的键值数据。
计算公式:CRC16(key) % 16384
使用哈希槽的好处就在于可以方便的添加或移除节点:
- 当需要增加节点时,只需要把其它节点的哈希槽挪到新节点即可
- 当需要移除节点时,只需要移除节点上的哈希槽挪到其它节点即可
另外,大家有想过这个问题吗,为什么 Redis Cluster 哈希槽的个数要设置为 16384?
CRC16 校验码计算结果是 2 个字节,16 位,所以槽的个数实际上最大取值可以是 2^16 = 65536。
- 如果槽位为 65536,发送心跳信息的消息头达 8k,发送的心跳包过于庞大,浪费带宽
- Redis 集群主节点数量基本不可能超过 1000 个,如果节点超过 1000 个,也会导致网络拥堵,那么对于节点数在 1000 以内的 Redis cluster 集群,16384 个槽位够用了,没必要拓展到 65536 个
- 槽位越小,节点少的情况下,压缩率高
2.集群如何扩容?
集群扩容通过重新分片来实现。
重新分片可以将已经分配给某个节点的任意数量的 slot 迁移给另一个节点,并且相关槽所属的键值对也会从源节点被移动到目标节点。
重新分片操作可以在线进行,在重新分片过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。
3.故障检测与转移
Redis 集群中的节点分为主节点(master)和从节点(slave),读&写请求其实都是在 master 上完成的,slave 节点只是充当了一个数据备份的角色,当 master 发生了宕机,就会将对应的 slave 节点提拔为 master,来重新对外提供服务。
简单来说,集群中的每个节点都会定期地向集群中其它节点发送 PING 消息,以此来检测对方是否在线,节点正常状态下接收到 PING 消息会返回一个 PONG 消息。针对 A 节点,某一个节点认为 A 宕机了,那么此时是疑似下线。而如果集群内超过半数的节点认为 A 挂了, 那么此时 A 就会被标记为已下线。
一旦节点 A 被标记为了已下线,集群就会开始执行故障转移。其余正常运行的 master 节点会进行投票选举,从 A 节点的 slave 节点中选举出一个,将其切换成新的 master 对外提供服务。当某个 slave 获得了超过半数的 master 节点投票,就成功当选。
故障转移步骤:
- 复制下线主节点的所有从节点中,会有一个从节点被选中
- 被选中的从节点会执行 SLAVEOF no one 命令,称为新的主节点
- 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己
- 新的主节点向集群广播一条 PONG 消息,这条 PONG 消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽
- 新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成
4.投票选举原理
当 slave(从节点)发现自己的 master(主节点)不可用时,便尝试进行故障切换,以便成为新的 master。由于挂掉的 master 可能会有多个 slave,从而存在多个 slave 竞争成为 master 节点的过程, 其选举过程如下:
- slave 发现自己的 master 进入下线状态
- slave 将记录集群的 currentEpoch(选举轮次标记)加 1,并广播信息给集群中其它节点
- 其他节点收到该信息,只有 master 响应,这个主节点尚未投票给其它从节点,那么第一个向主节点要求投票的从节点获得主节点的投票
- 尝试选举的 slave 收集 master 返回的结果,收到 超过半数 master 的投票后变成新 master
- 如果选举成功,广播 pong 消息通知其他集群节点;如果这一轮选举轮次中没有从节点能够收集到足够多的支持票,那么重新选举,直到选出新的主节点为止
5.槽指派
实质上,前面都说的比较笼统,下面我们好好分析一下从 Redis 集群的构建到槽指派过程。
一个 Redis 集群通常由多个节点组成,通过向指定节点发送 cluster meet 命令,可以让与指定节点进行握手,如果握手成功,就可以将指定节点添加到当前节点所在集群中。
通过向节点发送 cluster addslots 命令,可以将一个槽或多个槽指定给节点负责,如:
当以上三个 cluster addslots 命令都执行完毕后,数据库中的 16384 个槽已经被指派给了相应的节点,集群进入上线状态。
一个节点除了会将自己负责处理的槽记录在 clusterNode 结构的 slots 属性和 numslots 属性外,它还会将自己的 slots 数组通过消息发送给集群中其它节点,以此来告知其它节点自己目前负责处理哪些槽。因此,集群中的每个节点都会知道数据库中的 16384 个槽分别被指派给了集群中的哪些节点。
clusterStatue 结构中的 slots 数组记录了集群中所有 16384 个槽的指派信息:
引入 clusterStatue 结构的好处在于:如果我们想要知道槽 i 被指定给了哪个节点,直接访问 clusterState.slots[i] 的即可,操作的时间复杂度为 O(1),如果去遍历 clusterNode 结构中 slots 数组,时间复杂度为 O(N)。
当我们执行 cluster addslots 命令后 cluseterState 的结构:
6.集群中执行命令
对数据库中的 16384 个槽进行了指派之后,集群就会进入上线状态,这时客户端就可以向集群中的节点发送数据命令了。
当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库属于哪个槽,并检查这个槽是否指派给了自己:
- 如果键所在的槽正好就指派给了当前节点,那么节点直接执行这个命令
- 如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个 MOVED 错误,指引客户端转向(redirect)至正确的节点,并再次发送之前想要执行的命令。
那如何来判断槽是否由当前节点处理?
当节点计算出键所属的槽 i 之后,节点就会检查自己在 clusterState.slots 数组中的项 i,判断键所在的槽是否由自己负责:
- 如果 clusterState.slot[i] = clusterState.myself,说明槽 i 由当前节点负责,节点可以执行客户端发送的命令
- 如果 clusterState.slot[i] != clusterState.myself,说明槽 i 并非由当前节点负责,节点会根据 clusterState.slot[i] 指向的 clusterNode 结构所记录的节点 IP 和端口号,向客户端返回 MOVED 错误,执行客户端转向至正在处理槽 i 的节点。
注意:集群模式的 redis-cli 客户端在接收到 MOVED 错误时,并不会打印出 MOVED 错误,而是根据 MOVED 错误自动进行节点转向,并打印出转向信息,所以我们无法看到节点返回的 MOVED 错误信息。
7.ASK 错误
在重新进行分片期间,源节点向目标节点迁移一个槽过程中,可能会出现这样一种情况:被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对保存在目标节点里面。
如:假设节点 7002 正在向节点 7003 迁移槽 16198,这个槽包含 “is” 和 “love” 两个键,其中 “is” 还留在节点 7002,而键 “love” 已经被迁移到了 7003。
如果我们向节点 7002 发送关于键 “is” 的命令,那么这个命令就会直接被节点 7002 执行。
而如果我们向节点 7002 发送关于键 “love” 的命令,那么客户端会先被转向至节点 7003,然后再次执行命令。
和接到 MOVED 错误时的情况类似,集群模式的 redis-cli 在接到 ASK 错误时也不会打印错误,而是自动根据错误提供的 IP 地址和端口进行转向动作。如果想看到节点发送的 ASK 错误的话,可以使用单机模式的 redis-cli 客户端。
8.Redis Cluster消息
Redis 集群各节点之间的通讯协议:gossip 协议。
gossip 协议的主要用途就是信息传播和扩散,它的基本思想:一个节点想要分享一些信息给网络中的其它的一些节点。于是,它周期性的随机选择一些节点,并把信息传递给这些节点。这些收到信息的节点接下来会做同样的事情,即把这些信息传递给其他一些随机选择的节点。
集群中的各个节点通过发送和接收消息来进行通信,节点发送的消息主要有以下 5 种:
-
MEET 消息
当发送者接到客户端发送的 CLUSTER MEET 命令时,发送者会向接收者发送 MEET 消息,请求接收者加入到发送者当前所处的集群中。
-
PING 消息
集群中每个节点默认每隔 1 秒钟就会从已经节点列表中随机选出 5 个节点,然后对这 5 个节点中最长时间没有发送过 PING 消息的节点发送 PING 消息,以此来检测被选中的节点是否在线。除此之外,如果节点 A 最后一次收到节点 B 发送的 PONG 消息的时间,距离当前时间已经超过了节点 A 的 cluster-node-timeout 选项设置时长的一半,那么节点 A 也会向节点 B 发送 PING 消息,这可以防止节点 A 因为长时间没有随机选中节点 B 作为 PING 消息的发送对象而导致节点 B 的信息更新滞后。
-
PONG 消息
当接受者收到发送者发来的 MEET 消息或者 PING 消息时,为了向发送者确认消息已到达,接收者会向发送者返回一条 PONG 消息。另外,一个节点也可以通过向集群广播自己的 PONG 消息来让集群中的其它节点立即刷新关于这个节点的认识。
-
FAIL 消息
当一个主节点 A 判断另一个主节点 B 已经进入 FAIL 状态时,节点 A 会向集群广播一条关于节点 B 的 FAIL 消息,所有收到这条消息的节点都会立即将节点 B 标记为已下线。
-
PUBLISH 消息
当节点接收到一个 PUBLISH 命令时,节点会执行这个命令,并向集群广播一条 PUBLISH 消息,所有接收到这条 PUBLISH 消息的节点都会执行相同的 PUBLISH 命令。
标签:Redis,发送,Cluster,集群,消息,master,原来,节点 来源: https://blog.csdn.net/qq_37205350/article/details/117855449