百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Redis原理—3.复制、哨兵和集群二

mhr18 2024-12-11 11:38 18 浏览 0 评论

大纲

1.Redis使用sync命令实现的复制功能

2.Redis使用psync命令实现的复制功能

3.Redis主从服务器之间的心跳检测

4.从服务器如何实现复制主服务器的(复制的实现)

5.Redis的复制拓扑介绍

6.Redis主从复制数据延迟的处理

7.Redis主从复制的问题

8.Redis Sentinel和高可用

9.Redis如何保存更多的数据

10.一个普通Redis服务器的初始化过程

11.一个Sentinel服务器的初始化过程

12.Sentinel如何向主从服务器获取信息和发送信息

13.Sentinel如何检测主客观下线并实现故障转移

14.Sentinel配置优化和部署技巧

15.Sentinel客户端的基本实现原理

16.Sentinel的基本实现原理(哨兵机制的基本流程)

17.Sentinel的高可用读写分离

18.关于Sentinel的一些问题

19.Redis Cluster集群的简介

20.Redis Cluster集群搭建的步骤

21.Redis Cluster集群执行命令的实现原理

22.Redis Cluster集群节点通信的实现原理

23.Redis Cluster集群复制与故障转移的实现原理

24.通过Smart客户端支持Redis Cluster集群

25.Redis Cluster集群的补充说明

26.Redis Cluster集群的倾斜问题

27.Redis Cluster集群的核心问题


19.Redis Cluster集群的简介

(1)Redis的分布式方案

(2)常见的分区规则

(3)常见的希分区规划

(4)Redis Cluster采用虚拟槽分区

(5)Redis Cluster存在的功能限制


(1)Redis的分布式方案

Redis Cluster是Redis的分布式解决方案,除了Redis Cluster之外,Redis的分布式方案还有:


一.客户端分区方案,优点是分区逻辑可控,缺点是需要自己处理数据路由高可用故障转移等问题


二.代理方案,优点是简化客户端分布式逻辑和升级维护便利,缺点是加重架构部署复杂度性能损耗


(2)常见的分区规则

分布式数据库首先要解决把整个数据集按照分区规划映射到多个节点的问题,常见的分区规则有哈希分区和顺序分区两种:


一.哈希分区,特点是离散度好数据分布与业务无关无法顺序访问,如Redis Cluster


二.顺序分区,特点是离散度易倾斜数据分布与业务相关可顺序访问,如HBase


(3)常见的希分区规划

一.节点取余分区

用数据的键或ID,根据节点数量,使用公式计算出哈希值:hash(key) % N,哈希值决定数据映射节点。


优点是:简单,常用于数据的分库分表规则。一般采用预分区方式提前根据数据量规划好分区数,如512或1024张表。扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况。


缺点是:扩容或收缩节点时,数据节点映射关系要重新计算,会导致大量数据的重新迁移缓存雪崩


二.一致性哈希分区(多用于缓存场景)

为系统中每个节点分配一个token,token数量在0~2^32之间,这些token构成一个哈希环。节点查找时,首先根据key计算哈希值,然后顺时针找到第一个大于该哈希值的token节点


优点是:相比于节点取余,加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。


缺点是:加减节点会造成部分数据无法命中,需手动处理或忽略这部分数据,因此一致性哈希常用于缓存场景。而且不适合少量数据节点的分布式方案,因为节点变化将大范围影响哈希环中的数据映射。普通的一致性哈希分区在增减节点时需要增加一倍或减去一半节点才能保证数据负载均衡,容易数据倾斜


三.虚拟槽分区(解决一致性哈希的缺点)

槽是集群内数据管理和迁移的基本单位,采用大范围槽的目的是方便数据拆分集群拓展


(4)Redis Cluster采用虚拟槽分区

Redis所有的键会根据哈希函数映射到0~16383整数槽内,计算公式是:slot = CRC16(key) & 16383。每个节点负责维护一部分槽及槽所映射的键值数据。


Redis Cluster虚拟槽分区的特点:

一.解耦数据和节点间的关系,简化节点扩容和缩容

二.节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区的元数据

三.支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景


(5)Redis Cluster存在的功能限制

一.key批量操作支持有限,如mset、mget目前只支持具有相同slot值的key执行批量操作

二.key事务操作支持有限,只支持具有相同slot值的key的事务操作

三.不能将一个大的键值对象如hash、list等映射到不同的节点

四.不支持多数据库空间,单机Redis默认支持16个数据库,集群模式只能使用1个数据库

五.从节点只能复制主节点,不支持树状复制结构


20.Redis Cluster集群搭建的步骤

可以使用redis-trib.rb create快速搭建集群。


步骤一:准备节点

Redis集群节点数量至少为6个,才能保证组成完整的高可用集群。每个节点需要开启配置cluster-enabled yes,让Redis运行在集群模式下。


步骤二:节点握手

节点握手是指集群模式下的各节点,通过Gossip协议彼此通信来感知对方。节点握手是集群彼此通信的第一步,由客户端发起命令:cluster meet


cluster meet命令是一个异步命令,执行之后立刻返回。只要一个节点向其他所有节点发起cluster meet命令即可,其他节点会相互自动发现完成握手


步骤三:分配槽

Redis集群把所有数据映射到16384个槽中,每个key会映射为一个固定的槽。通过命令cluster addslots可以为节点分配槽,通过命令cluster replicate可以让一个节点成为从节点。


节点握手完成后,集群处于下线状态。槽分配完成后,集群才进入在线状态


21.Redis Cluster集群执行命令的实现原理

(1)集群的数据结构

(2)节点的3次握手和cluster meet命令的原理

(3)Redis Cluster的槽指派

(4)在Redis Cluster中执行命令的部分流程

(5)在Redis Cluster中执行命令的部分源码

(6)Redis Cluster返回客户端的MOVED错误

(7)Redis Cluster集群节点的数据库

(8)重新分片 + 迁移槽数据 + 集群伸缩

(9)Redis Cluster返回客户端的ASK错误

(10)在Redis Cluster中执行命令的完整流程


(1)集群的数据结构

每个节点都会保存着一个ClusterState结构,这个结构记录了当前节点视角下集群目前所处的状态。每个节点都会使用一个ClusterNode结构来记录自己的状态,并为集群中的所有节点都创建出一个相对应的ClusterNode结构。


一.ClusterState结构

ClusterNode *myself; //指向当前节点的指针
dict *nodes; //集群节点名单,键为节点名字,值为节点对应的clusterNode结构
ClusterNode *slots[16384]; //每个槽指派的节点信息,每个数组项是一个指向clusterNode结构的指针
zskiplist *slots_to_keys; //保存槽和键关系的跳跃表
ClusterNode *migrating_slots_to[16384]; //当前节点正在迁移至其他节点的槽
ClusterNode *importing_slots_to[16384]; //当前节点正在从其他节点导入的槽

二.ClusterNode结构

char name[]; //节点名字
int flags; //节点标记(主节点还是从节点,在线还是下线)
char ip[]; //节点的IP地址
int port; //节点的端口号
char slots[]; //二进制位数组,记录了节点负责处理哪些槽,若节点处理了槽i,则slots[]数组在索引i的值为1
int numslots; //节点处理的槽数
ClusterNode *slaveof; //如果当前节点是从节点,那么这是一个指向主节点的指针
ClusterNode **slaves; //如果当前节点是主节点,那么每个数组项都指向其从节点的clusterNode结构
int numslaves; //正在复制这个主节点的从节点数量
list *fail_reports; //一个链表,记录了所有其他节点对该节点的下线报告
ClusterLink *link; //保存连接节点所需的有关信息

(2)节点的3次握手和cluster meet命令的原理

客户端向节点A发送命令cluster meet请求节点A与节点B进行握手,收到命令的节点A将会与节点B进行握手,以此来确认彼此的存在,为将来的进一步通信打好基础。


首先,节点A会为节点B创建一个ClusterNode结构,并将该结构添加到自己的ClusterState.nodes字典里。然后,节点A根据cluster meet命令给定的IP地址和端口号,向节点B发送一条meet消息


节点B收到节点A发送的meet消息后,节点B会为节点A创建一个ClusterNode结构,并将该结构添加到自己的ClusterState.nodes字典里,然后节点B会向节点A返回一条pong消息


节点A收到节点B返回的pong消息,此时节点A可知节点B已成功收到自己发送的meet消息。然后,节点A将向节点B返回一条ping消息


节点B收到节点A返回的ping消息,此时节点B可知节点A已成功收到自己返回的pong消息,握手完成


(3)Redis Cluster集群的槽指派

一.数据库分为16384个槽

Redis集群的整个数据库被分为16384个槽(slot)。当数据库中的16384个槽都有节点在处理时,集群处于上线状态,否则处于下线状态


二.记录节点的槽指派信息

ClusterNode结构的slots属性和numslots属性,记录了节点负责处理哪些槽。slots属性是一个二进制位数组,拥有16384个元素。若节点处理了槽i,则slots数组在索引i的值为1。检查节点是否处理某个槽或者将某个槽指派给节点负责,复杂度都是O(1)。


三.传播节点的槽指派信息

一个节点除了会将自己负责的槽记录在ClusterNode结构的slots属性和numslots属性之外,还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前处理哪些槽。所以集群中的每个节点都会知道数据库中的16384个槽分别被指派给了哪些节点


四.记录集群所有槽的指派信息

ClusterState结构中的slots数组记录了集群中所有16384个槽的指派信息。虽然ClusterNode.slots数组记录了单节点的槽指派信息,但ClusterState.slots数组记录集群所有槽指派信息依然很有必要。因为通过这两个slots数组,可以让查找槽i被指派到哪个节点以及查找节点的所有槽的时间复杂度变为O(1)


(4)在Redis Cluster中执行命令的部分流程

当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出键属于哪个槽。如果当前节点就是负责处理键所在槽的节点,那么节点就会执行命令。否则,节点会向客户端返回一个MOVED错误,客户端根据MOVED错误提供的信息转向正确节点


请求路由一:节点对于不是它的键命令只回复MOVED错误进行重定向响应并不负责转发


请求路由二:节点对于是它的键命令找不到时会检查槽是否迁移,如果是槽正在迁移,则回复ASK错误进行重定向响应


(5)在Redis Cluster中执行命令的部分源码

首先计算键属于哪个槽:i = CRC16(key) & 16384,然后判断槽i是否由当前节点负责处理。


ClusterState.slots[i] = ClusterState.myself,说明槽i由当前节点负责,那么当前节点可以执行客户端的命令。


ClusterState.slots[i] != ClusterState.myself,说明槽i并非由当前节点负责,那么节点会根据ClusterState.slots[i]指向的ClusterNode结构记录的IP和端口,向客户端返回MOVED错误,指引客户端转向正确处理槽i的节点。


(6)Redis Cluster返回客户端的MOVED错误

当节点发现键所在的槽并非由自己负责处理的时候,节点就会向客户端返回一个MOVED错误,指引客户端根据MOVED错误中提供的IP和端口,转向负责处理这个槽的节点重新发送命令


(7)Redis Cluster集群节点的数据库

集群节点保存键值对以及键值对过期时间的处理方式,和单机完全相同。集群节点只能使用0号数据库,但会用ClusterState结构中的slots_to_keys跳跃表保存槽和键的关系。slots_to_keys跳跃表的每个节点的分值是槽号成员是键,因此可以通过slots_to_keys快速获取某个槽的所有数据库键


(8)重新分片 + 迁移槽数据 + 集群伸缩

可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一节点(目标节点)。重新分片操作可以在线进行,进行过程中集群不需要下线,且源节点和目标节点继续正常提供服务。


Redis Cluster集群的重新分配操作由集群管理软件redis-trib负责执行,步骤如下。


步骤一:对目标节点发送cluster setslotimporting命令,让目标节点准备导入槽的数据

步骤二:对源节点发送cluster setslotmigrating命令,让源节点准备迁出槽的数据

步骤三:向源节点发送cluster getkeyinslot命令,获取count个属于槽的键

步骤四:通过pipeline批量迁移获取的键到目标节点

步骤五:重复三四直到槽下所有键值迁移到目标节点

步骤六:向集群中任意一个节点发送cluster setslotnode命令,通知槽已分配给目标节点


注意:下线节点的情况

由于集群的节点不停地通过Gossip消息彼此交换节点状态,因此需要通过一种健壮的机制让集群内所有节点忘记下线的节点,让其他节点不再与下线节点进行Goosip消息交换。当节点收到cluster forget命令后,会把down_id指定的节点加入到禁用列表中,该列表中的节点不再发消息。


(9)Redis Cluster返回客户端的ASK错误

在重新分片期间,源节点向目标节点还没完成槽的迁移。这时当客户端向源节点发送关于键key的命令,且key恰好属于正在被迁移的槽时:

一.源节点会先在自己的数据库里查找key,如果找到就直接执行命令

二.如果找不到,则该key有可能已经被迁移到目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点,并重新发送命令


(10)在Redis Cluster中执行命令的完整流程

节点收到一个关于键key的命令请求:


一.如果键key所属的槽i正好是指派给自己的,则节点会尝试在自己的数据库里查找键key。


如果可以找到,那么节点就直接执行客户端发送的命令。如果实在找不到,那么节点将检查自己的ClusterState.migrating_slots_to[i],看看槽是否正在迁移。若正在迁移,则节点会向客户端返回一个ASK错误,引导客户端去目标节点找键key。


二.如果键key所属的槽i不是指派给自己的,则节点会向客户端返回一个MOVED错误


当客户端接收到节点返回的MOVED错误时,客户端会根据错误提供的IP和端口,转向至负责槽的节点,并向该节点重新发送之前想要执行的命令


当客户端接收到节点返回的ASK错误时,客户端会根据错误提供的IP和端口,转向至正在导入槽的目标节点,然后首先向目标节点发送一个ASKING命令,之后再重新发送要执行的命令


ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING命令(一次性命令)。通常如果客户端向节点发送一个关于槽i的命令,而槽i又没有指派给这个节点的话,那么节点将向客户端返回一个MOVED错误。但是如果节点的ClusterState.importing_slots_from[i]显示节点正在导入槽i,并且发送命令的客户端带有REDIS_ASKING标识,那么节点将破例执行这个关于槽i的命令一次。


22.Redis Cluster集群节点通信的实现原理

(1)采用Gossip协议维护节点元数据

(2)Gossip协议下的Redis集群通信过程

(3)Gossip协议下的ping消息

(4)Gossip协议下的meet消息

(5)Gossip协议下的pong消息

(6)Gossip协议下的fail消息

(7)Gossip协议下的publish消息

(8)meet、ping、pong消息说明


(1)采用Gossip协议维护节点元数据

分布式存储中需要提供维护节点元数据信息的机制,其中元数据是指:节点负责的数据是否出现故障。常见的元数据维护方式为:集中式P2P方式


Redis Cluster集群采用P2P的Gossip协议。Gossip协议的工作原理就是:节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整信息。


(2)Gossip协议下的Redis集群通信过程

说明一:集群中的每个节点都会单独开辟一个TCP通道,用于节点间彼此通信,通信端口号在基础端口号上加10000

说明二:每个节点在固定周期内通过特定规则选择几个节点发送ping消息

说明三:接收到ping消息的节点用pong消息响应


集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点。只要这些节点可以正常通信,最终会达到一致的状态,实现集群状态的同步。


节点出现故障新节点加入主从角色变化槽信息变更时,通过不断的ping、pong消息通信,那么经过一段时间后,所有的节点都会知道整个集群全部节点的最新状态。


(3)Gossip协议下的ping消息

集群里每个节点默认每隔一秒就会从已知节点列表中随机选出5个节点,然后对这5个节点中最长时间没有发送过ping消息的节点发送ping消息,以此来检测被选中的节点是否在线


此外,如果节点A最后一次收到节点B发送的pong消息时间,距离当前时间已超过了cluster_node_timeout选项设置时长的一半,那么节点A也会向节点B发送ping消息,防止节点A长时间没选中节点B


ping消息不仅会封装自身节点的状态数据,还会封装其他节点的状态数据


(4)Gossip协议下的meet消息

节点A向节点B发送meet消息,请求节点B加入到节点A当前所处的集群里。通过meet消息完成节点握手后,节点B会加入集群并进行周期性的ping、pong消息交换。


(5)Gossip协议下的pong消息

当节点B接收到节点A的ping、meet消息时,会给节点A发送pong消息确认消息通信正常


一个节点也可以通过向集群广播自己的pong消息,以此来让集群中的其他节点立即刷新关于这个节点的认知。比如一次故障转移成功后,新的主节点会向集群广播一条pong消息,从而让集群中其他节点立即知道新节点接管槽


(6)Gossip协议下的fail消息

当节点判定集群内另外一个节点下线时,会向集群广播一个fail消息。其他节点接收到fail消息后,会把对应节点更新为下线状态。


当集群里的主节点A将主节点B标记为已下线时,主节点A将向集群广播一条关于主节点B的fail消息。所有接收到这条fail消息的节点都会将节点B标为已下线,因为单纯使用Gossip协议来传播节点的已下线信息会给节点信息的更新带来延迟


(7)Gossip协议下的publish消息

当节点收到客户端一个publish命令时,节点会执行这个命令,并向集群广播一条publish消息,然后所有接收到这个publish消息的节点都会执行相同的publish命令


当客户端向集群中的某个节点发送publish命令publish的时候,接收到publish命令的节点不仅会向自己的channel频道发送消息message,还会向集群广播一条publish消息。所有接收到这条public消息的节点都会向自己的channel频道发送message消息,这样订阅了集群的channel频道的客户端才能确保收到消息。


(8)meet、ping、pong消息说明

每次发送meet、ping、pong消息时,发送者都从自己的已知节点列表中随机选出两个节点,并将这两个被选中的节点的信息保存在消息正文里。


假设在一个包含A、B、C、D、E、F六个节点的集群里,节点A向节点B发送ping消息,并且消息里包含节点C和节点D的信息。当节点B收到这条ping消息时,它将更新对节点C和节点D的认知(创建或更新ClusterNode结构)。之后节点B将向节点A返回一条pong消息,并且消息里包含节点E和节点F的消息。当节点A收到这条pong消息时,它将更新对节点E和节点F的认知(创建或更新ClusterNode结构)。


23.Redis Cluster集群复制与故障转移的实现原理

(1)节点的复制方法

(2)故障发现流程

(3)主观下线

(4)客观下线

(5)尝试客观下线

(6)故障恢复的流程

(7)故障转移的时间


(1)节点的复制方法

步骤一:向节点发送cluster replicate命令,可以让接收该命令的节点成为node_id所指定节点的从节点,并开始对主节点进行复制。


步骤二:接收到该命令的节点会在自己的ClusterState.nodes字典中找到node_id所对应节点的ClusterNode结构,并将自己的ClusterState.myself.slaveof指针指向这个结构,以此来记录这个节点正在复制的主节点。


步骤三:然后,这个节点会修改自己在ClusterState.myself.flags中的属性,关闭原本的REDIS_NODE_MASTER标识,打开REDIS_NODE_SLAVE标识,表示该节点已经由原来的主节点变成了从节点


步骤四:最后,这个节点会调用复制代码,并根据ClusterState.myself.slaveof指针指向的ClusterNode结构所保存的IP地址和端口号,对主节点进行复制


一个节点成为从节点并开始复制某个主节点的信息,会通过消息发送给集群中的其他节点。最终集群中的所有节点都会知道某个从节点正在复制某个主节点。


集群中的所有节点都会在代表主节点的ClusterNode结构的slaves属性和numslaves属性中,记录正在复制这个主节点的从节点名单。


(2)故障发现流程

Redis集群内节点通过ping、pong消息实现节点通信,消息可以传播节点槽信息主从状态节点故障等。因此故障发现也是通过消息传播机制实现的,主要环节包括主观下线客观下线


主观下线是指:某个节点认为另一个节点不可用,即下线状态,这个状态只能代表一个节点的意见,可能存在误判。


客观下线是指:标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。


如果是持有槽的主节点故障,需要为该节点进行故障转移。Redis集群自动故障转移过程分为故障发现故障恢复


Redis集群对故障节点的发现流程是:

主观下线 -> Gossip消息传播 -> 保存到下线报告链表 -> 尝试客观下线 -> 广播fail消息 -> 故障转移


(3)主观下线

集群内每个节点都会定期向其他节点发送ping消息,接收到ping消息的节点会回复pong消息作为响应。如果在cluster_node_timeout时间内某节点无法与另一个节点顺利完成ping消息通信,则将该节点标记为主观下线


其中,主观下线的识别流程如下:


首先,节点A发送ping消息给节点B。


如果节点A与节点B通信正常,节点B将接收到ping消息并返回pong消息给节点A,节点A会更新最近一次与节点B的通信时间


如果节点A与节点B通信出现问题,则断开连接,下次会进行重试


如果节点A与节点B一直通信失败,则节点A记录的与节点B的最后通信时间将无法更新


当节点A内的定时任务检测到与节点B最后的通信时间超过cluster_node_timeout时,将更新本地节点B的状态为主观下线(pfail)


(4)客观下线

当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消息在集群内传播。当接收节点发现消息中含有主观下线的节点状态时,会在本地找到故障节点的ClusterNode结构并保存到下线报告链表中


通过Gossip消息传播,集群内的节点会不断收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点是主观下线时,将标记节点客观下线(fail)广播fail消息


为什么必须是负责槽的主节点参与故障发现决策

因为集群模式下只有处理槽的主节点才负责读写请求和集群槽等关键信息维护,从节点只进行复制。


为什么要半数以上持有槽的主节点标记主观下线?

必须半数以上是为了应对网络分区等原因造成的集群分隔情况。


下线报告的有效期限是:cluster_node_timeout * 2,主要针对故障误报的情况。因此不建议将cluster_node_timeout设置得过小,以防下线报告过期而无法收集到一半以上的下线报告。


(5)尝试客观下线

集群中的节点每次接到其他节点的主观下线信息时,都会尝试客观下线,流程如下:


首先统计有效的下线报告数量,如果小于集群内持有槽的主节点数量的一半,则退出。当下线报告数量大于持有槽的主节点数量的一半时,标记对应的故障节点为客观下线。然后向集群广播一条fail消息,通知所有节点将故障节点标记为客观下线(fail)


当网络出现分区时,可能集群会被分割为一大一小两个独立集群。大的集群内持有槽的主节点超半数,可以完成客观下线并广播fail消息,但小集群无法收到fail消息。所以部署节点时要避免故障节点所有的从节点都在小集群内而无法完成后续的故障转移,降低主从被分区的可能。


(6)故障恢复的流程

Redis Cluster集群的自动故障转移过程整体上分为故障发现故障恢复两部分。


当一个从节点发现自己正在复制的主节点进入了客观下线状态时,该从节点将开始对下线主节点进行故障转移恢复。


故障恢复的流程是:资格检查 -> 准备选举时间 -> 发起选举 -> 选举投票 -> 替换主节点


一.资格检查

如果从节点与主节点断线时间超过:cluster_node_time*cluster_slave_validity_factor,则当前从节点不具备故障转移资格。其中cluster_slave_validity_factor是从节点有效因子,默认为10。


二.准备选举时间

从节点具备故障转移资格后,更新发起选举的时间,只有到达该时间后才能发起选举。选举时间采用延迟触发机制,复制偏移量最大的延迟是1秒,复制偏移量越小延迟越长。


三.发起选举

当从节点定时任务检测发起选举的时间到达后,便执行发起选举。首先会更新配置纪元,从节点每次发起选举时都会自增集群的配置纪元。然后会广播选举消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票。


注意:节点消息传播时,为了防止过期消息,会以配置纪元大的一方为准。


四.选举投票

只有持有槽的主节点才会向从节点投票,每个持有槽的主节点在一个配置纪元内只能投一次票。Redis集群没有使用从节点来投票,是因为当从节点只有1个时无法凑够半数以上的票。


五.替换主节点

当从节点收到半数以上持有槽的主节点的投票后,这个从节点便被选为新的主节点。


首先,当前从节点会执行slaveof no one命令成为主节点。然后,新的主节点会执行clusterDelSlot撤销故障主节点负责的槽,执行clusterAddSlot将这些槽委派给自己。接着,新的主节点会向集群广播pong消息,让集群中所有节点立即知道自己已经由从节点变成了主节点,并接管了槽。


(7)故障转移的时间

一.主观下线识别时间 = cluster_node_timeout

二.主观下线状态消息传播时间 <= cluster_node_timeout / 2

三.从节点转移时间 <= 1000毫秒


由于存在延迟发起选举的机制,偏移量最大的从节点会最多延迟1秒发起选举。通常第一次选举就会成功,所以从节点执行转移时间在1秒以内。

故障转移时间 <= cluster_node_timeout + cluster_node_timeout / 2 + 1000

其中cluster_node_timeout的值默认是15s,但又不是越小越好,越小越增加Gossip消息的频率,增加带宽消耗。


24.通过Smart客户端支持Redis Cluster集群

(1)MOVED错误重定向的优缺点

(2)Smart客户端的简介

(3)Smart客户端操作集群的流程

(4)Smart客户端可能存在的问题

(5)Smart客户端针对批量操作、Lua和事务的处理


(1)MOVED错误重定向的优缺点

优点:客户端能随机连接集群内任意一个Redis获取键所在节点,代码简单,对客户端协议影响小。


缺点:每次执行键命令前都要到Redis上进行重定向才能找到要执行命令的节点,额外增加了IO开销


因此,集群客户端通常采用Smart客户端


(2)Smart客户端的简介

大多数语言的Redis客户端都采用了Smart客户端来支持集群协议。Smart客户端通过在内部维护槽和节点的映射关系本地就可以实现键到节点的查找,从而保证IO最大化。而MOVED错误重定向会协助Smart客户端更新槽和节点的映射的缓存


(3)Smart客户端操作集群的流程

首先,Smart客户端在初始化时随机选择一个运行节点,使用cluster slots命令来初始化槽和节点的映射关系


然后,Smart客户端会解析cluster slots命令的返回结果缓存slots在本地,然后为每一个节点创建唯一的连接(池)。


之后,Smart客户端执行键命令时,首先会计算键的slot并根据本地slots缓存获取目标节点连接,然后发送命令。


如果出现连接错误,则随机找出活跃节点发送命令,且最多重试5次,否则执行异常。


如果收到MOVED错误,则使用cluster slots命令更新本地slots缓存,然后向正确的节点发送命令。


(4)Smart客户端可能存在的问题

问题一:客户端内部维护本地slots缓存表,并且针对每个节点维护连接池,当集群规模非常大时,客户端要维护非常多的连接并消耗更多的内存。


问题二:可能在并发量高的场景下出现cluster slots风暴和写锁阻塞问题。客户端是要获得写锁才能执行cluster slots命令的,而计算槽对应的节点需要使用读锁去访问本地slots缓存。读写锁是读锁共享、读锁和写锁互斥,并发量大且故障正在转移(宕机的节点还没恢复)时会导致请求阻塞,影响集群吞吐。


改进措施:

一.降低cluster slots命令的调用次数

二.降低写锁持有时间,如对执行cluster slots命令不加锁,只对修改本地slots缓存时才加锁,而且修改前对比新获取的slots映射是否变化才加写锁进行更新


(5)Smart客户端针对批量操作、Lua和事务的处理

一.批量操作

可以利用Smart客户端保存的槽和节点的映射关系,对要批量处理的key进行归档,然后对每个节点对应的子key列表执行mget或者pipeline操作。


二.Lua和事务

Lua和事务所操作的key必须在一个节点上,Smart客户端提供了hashtag。首先将事务中所有的key添加hashtag,然后使用CRC16计算hashtag对应的slot,获取指定slot对应的节点连接池并执行事务。


使用Smart客户端操作集群可以实现通信效率最大化,客户端内部负责计算和维护键->槽->节点的映射,用于快速定位键命令到目标节点


集群获取Smart客户端全面高效的支持需要一个过程,用户在选择Smart客户端时建议review集群交互代码,比如异常判定、重试逻辑、更新槽的并发控制等。


对于ASK错误重定向,客户端不会更新本地槽节点的缓存。对于MOVED错误重定向,客户端需要更新本地槽节点的缓存


25.Redis Cluster集群的补充说明

(1)集群的完整性

(2)集群的带宽消耗

(3)集群的发布订阅功能

(4)集群的读写分离


(1)集群的完整性

默认情况下,当集群16384个槽任何一个没有指派到节点时,那么整个集群都不可用。当持有槽的主节点下线时,从故障发现到自动完成转移期间,整个集群也是不可用的


建议将参数cluster_require_full_coverage配置为no,这样当主节点故障时只影响它负责槽的相关命令,不影响其他主节点的可用性


(2)集群的带宽消耗

集群内Gossip消息通信会消耗带宽,官方建议集群最大规模在1000以内,因此:

一.尽量避免大集群,同一系统可针对不同业务场景拆分使用多套集群

二.适度提高cluster_node_timeout降低消息发送频率,但cluster_node_timeout会影响故障转移速度

三.避免集中部署,如60个节点的集群,3台机器每台部署20个节点,带宽消耗将异常严重


(3)集群的发布订阅功能

集群模式下单节点收到的客户端publish命令都会向所有的节点进行广播,加重带宽负担。所以需要频繁使用发布订阅功能时,应避免在大量节点的集群使用,否则严重消耗网络带宽。建议使用Sentinel结构专门用于发布订阅功能


(4)集群的读写分离

集群模式下从节点不接收任何读写请求,发给从节点的键命令会MOVED重定向到负责槽的主节点上。但当从节点需要分担主节点的读压力时,可以使用readonly命令打开只读状态。


集群模式下的读写分离,同样和哨兵主从复制的读写分离一样遇到:数据延迟读到过期数据从节点故障等问题。


集群模式下的读写分离成本比较高,可以直接扩展主节点数量来扩展集群性能,一般不建议集群模式下做读写分离


26.Redis Cluster集群的倾斜问题

(1)数据倾斜

(2)请求倾斜


集群倾斜指不同节点之间数据量和请求量出现明显差异,加大负载均衡和开发运维难度。


(1)数据倾斜

一.节点和槽分配严重不均

使用redis-trib.rb info可以列举出每个节点负责的槽和键总量以及每个槽平均键数量。当节点和槽分配不均时,可用redis-trib.rb rebalance进行平衡。


二.不同槽对应键数量差异过大

键通过CRC16哈希函数映射到槽上,正常情况下槽内键数量会相对均匀。但当大量使用hash_tag时,会产生不同的键映射到同一个槽的情况。

cluster countkeysinslot <slot> 可以获取槽对应的键数量
cluster getkeysinslot <slot> <count> 循环迭代出槽所有的键,从而发现使用hash_tag的键

三.集合对象包含大量元素

通过命令redis-cli--bigkeys找出大集合对象,然后根据业务场景进行拆分。


四.节点内存倾斜

当集群大量使用hash、set等数据结构时,若hash-max-ziplist-value等压缩列表使用条件在各节点不一致时,那么就可能出现节点内存倾斜。


(2)请求倾斜

若热点键对应的是小对象的get、set等,那么即使请全量各节点差异较大一般也不会产生负载严重不均。


若热点键对应的是大对象的hgetall、smembers等复杂度高的命令,则可能会导致对应节点负载过高。


解决方法如下:

一.合理设计键,热点大集合对象做拆分或使用hmget替代hgetall避免整体读取

二.不要对热点键使用hash_tag避免热点键都在同一槽同一节点

三.若一致性要求不高,可以使用本地缓存减少热点键的调用


27.Redis Cluster集群的核心问题

(1)请求路由

(2)数据迁移


Redis Cluster方案就是为了解决单个节点数据量大、写入量大时产生的性能瓶颈问题。多个节点组成一个集群,可以提高集群的性能和可靠性,但随之而来的是集群的管理问题。最核心的问题就两个:请求路由数据迁移,当然其他问题也包括:扩容缩容数据平衡


(1)请求路由

Redis Cluster在每个节点记录完整的槽和节点的映射关系,以便于纠正客户端的错误路由请求。同时也发给客户端让客户端也缓存一份,便于客户端直接找到指定节点。客户端与服务端配合完成数据的路由,这需要业务在使用Redis Cluster时,必须升级为集群版的SDK才支持这种配合。


其他Redis集群方案如TwemproxyCodis都是增加代理层,客户端通过代理层对整个集群进行操作。代理层后面可以挂很多实例,由代理层维护请求路由。客户端不需要更换SDK,但代理层会带来性能损耗。


(2)数据迁移

判断一个集群方案是否成熟的标准是:数据迁移过程中是否影响业务。


Twemproxy不支持在线扩容,它只解决了请求路由的问题,它扩容时需要停机,对业务影响大。Redis Cluster和Codis都支持在线扩容,但数据迁移过程中会存在key的访问重定向,这时访问延迟会变大。数据迁移完成后,Redis Cluster需要客户端更新路由缓存,而Codis会在代理层更新路由表让客户端整个过程无感知


Redis Cluster的数据迁移是同步的,迁移一个key会同时阻塞源节点和目标节点,迁移过程有性能问题。Codis的数据迁移是异步的,迁移速度更快,对性能影响最小,实现也比较复杂。

相关推荐

一文读懂Prometheus架构监控(prometheus监控哪些指标)

介绍Prometheus是一个系统监控和警报工具包。它是用Go编写的,由Soundcloud构建,并于2016年作为继Kubernetes之后的第二个托管项目加入云原生计算基金会(C...

Spring Boot 3.x 新特性详解:从基础到高级实战

1.SpringBoot3.x简介与核心特性1.1SpringBoot3.x新特性概览SpringBoot3.x是建立在SpringFramework6.0基础上的重大版...

「技术分享」猪八戒基于Quartz分布式调度平台实践

点击原文:【技术分享】猪八戒基于Quartz分布式调度平台实践点击关注“八戒技术团队”,阅读更多技术干货1.背景介绍1.1业务场景调度任务是我们日常开发中非常经典的一个场景,我们时常会需要用到一些不...

14. 常用框架与工具(使用的框架)

本章深入解析Go生态中的核心开发框架与工具链,结合性能调优与工程化实践,提供高效开发方案。14.1Web框架(Gin,Echo)14.1.1Gin高性能实践//中间件链优化router:=...

SpringBoot整合MyBatis-Plus:从入门到精通

一、MyBatis-Plus基础介绍1.1MyBatis-Plus核心概念MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提...

Seata源码—5.全局事务的创建与返回处理

大纲1.Seata开启分布式事务的流程总结2.Seata生成全局事务ID的雪花算法源码3.生成xid以及对全局事务会话进行持久化的源码4.全局事务会话数据持久化的实现源码5.SeataServer创...

Java开发200+个学习知识路线-史上最全(框架篇)

1.Spring框架深入SpringIOC容器:BeanFactory与ApplicationContextBean生命周期:实例化、属性填充、初始化、销毁依赖注入方式:构造器注入、Setter注...

OpenResty 入门指南:从基础到动态路由实战

一、引言1.1OpenResty简介OpenResty是一款基于Nginx的高性能Web平台,通过集成Lua脚本和丰富的模块,将Nginx从静态反向代理转变为可动态编程的应用平台...

你还在为 Spring Boot3 分布式锁实现发愁?一文教你轻松搞定!

作为互联网大厂后端开发人员,在项目开发过程中,你有没有遇到过这样的问题:多个服务实例同时访问共享资源,导致数据不一致、业务逻辑混乱?没错,这就是分布式环境下常见的并发问题,而分布式锁就是解决这类问题的...

近2万字详解JAVA NIO2文件操作,过瘾

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。从classpath中读取过文件的人,都知道需要写一些读取流的方法,很是繁琐。最近使用IDEA在打出.这个符号的时候,一行代...

学习MVC之租房网站(十二)-缓存和静态页面

在上一篇<学习MVC之租房网站(十一)-定时任务和云存储>学习了Quartz的使用、发邮件,并将通过UEditor上传的图片保存到云存储。在项目的最后,再学习优化网站性能的一些技术:缓存和...

Linux系统下运行c++程序(linux怎么运行c++文件)

引言为什么要在Linux下写程序?需要更多关于Linux下c++开发的资料请后台私信【架构】获取分享资料包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdf...

2022正确的java学习顺序(文末送java福利)

对于刚学习java的人来说,可能最大的问题是不知道学习方向,每天学了什么第二天就忘了,而课堂的讲解也是很片面的。今天我结合我的学习路线为大家讲解下最基础的学习路线,真心希望能帮到迷茫的小伙伴。(有很多...

一个 3 年 Java 程序员 5 家大厂的面试总结(已拿Offer)

前言15年毕业到现在也近三年了,最近面试了阿里集团(菜鸟网络,蚂蚁金服),网易,滴滴,点我达,最终收到点我达,网易offer,蚂蚁金服二面挂掉,菜鸟网络一个月了还在流程中...最终有幸去了网易。但是要...

多商户商城系统开发全流程解析(多商户商城源码免费下载)

在数字化商业浪潮中,多商户商城系统成为众多企业拓展电商业务的关键选择。这类系统允许众多商家在同一平台销售商品,不仅丰富了商品种类,还为消费者带来更多样的购物体验。不过,开发一个多商户商城系统是个复杂的...

取消回复欢迎 发表评论: