「一发入魂」Redis集群模式之Cluster方案

本章内容

Cluster模式

在实际开发过程中,Redis的哨兵模式已经能基本满足了服务的高可用,但它存在两个问题:

  • 1)容量问题,单主节点只支持垂直扩容,不支持水平扩容。
    • 垂直扩容:升级单个Redis节点的资源配置,包括增加内存容量、增加磁盘容量、使用更高配置的CPU。
    • 水平扩容:横向增加Redis节点个数。
  • 2)并发性能问题,Redis单节点10万并发,存在性能瓶颈。

因此,Redis3.0之后引入了Redis Cluster集群模式,用来解决分布式扩展的需求,同时也实现了服务高可用机制。

Redis Cluster由多个Redis节点组构成,不同节点组中的数据没有交集(即:每个节点组对应数据分片中的一个分片)。节点组内部分为主备两类节点(即:主节点和从节点),两者数据准实时一致,通过异步化的主备复制机制来保证。

节点组有且只有一个主节点,可以有0~N个从节点,节点组中只有主节点对外提供写服务,读服务可以由主节点或从节点提供。一般一个Redis Cluster集群至少要包含6个节点才能保证高可用。如图所示:

图中:

  • 绿色单箭头连线表示客户端请求。
  • 橙色单箭头连线表示主从同步。
  • 黑色双箭头连线表示Gossip协议。

其中:三个Master节点会分配不同的Slot(即:哈希槽),当Master宕机时,Slave会自动选举为新的Master继续提供服务。

Cluster模式实现原理

Gossip协议

Redis集群采用基于P2P的Gossip协议,Gossip协议(又称流行病协议)指的是在节点数量有限的网络中,每个节点都会随机(不是真正随机,而是根据规则选择通信节点)与部分节点通信,经过一番杂乱无章的通信后,每个节点的状态在一定时间内会达成一致。如图所示:

规则如下:

  • 1)Gossip会周期性的散播消息,假设周期限定为1秒。
  • 2)被感染节点随机选择k个邻接节点(fan-out)散播消息,假设fan-out设置为3,每次最多往3个节点散播。
  • 3)每次散播消息都会选择限定周期内尚未收到过的节点进行散播。
  • 4)收到过消息的节点在限定周期内不再往发送节点散播,比如:A -> B,那么在1秒内B不再往A发送消息。

图中一共有9个节点,节点1为初始被感染节点,通过Gossip过程,最终所有节点都被感染。

Gossip协议消息

Gossip协议消息由消息头+消息体组成,它包含多种类型消息(如:ping消息、pong消息、fail消息、meet消息等):

  • ping消息:集群内每个节点每秒会向其他节点发送ping消息,用来检测其他节点是否正常工作,并交换彼此的状态信息。ping消息中会包括自身节点的状态数据和其他部分节点的状态数据。
  • pong消息:当接收到ping消息或meet消息时,会向发送者回复pong消息,pong消息中包括自身节点的状态信息。节点也可以通过广播的方式发送自身的pong消息来通知整个集群对自身状态的更新。
  • fail消息:当一个节点判定另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息后,把对应节点更新为下线状态。
  • meet消息:当向集群中加入新节点时,需要集群中的某个节点通过cluster meet <ip> <port>命令发送meet消息到新节点,通知新节点加入集群。新节点收到meet消息后,会回复pong命令给发送者。

节点通信

Redis Cluster集群中的每个Redis节点监听两个TCP端口:

  • 6379(默认):服务端口。
  • 16379(默认服务端口+10000):集群内部通信端口。

通信过程:

  • 1)集群中的每个节点都会单独开辟一个TCP通道,用于节点之间彼此通信,通信端口号在基础端口上加10000。
  • 2)每个节点在固定周期内通过特定规则选择几个节点发送ping消息。
  • 3)接收到ping消息的节点回复pong消息作为响应。

集群中每个节点通过一定规则挑选要通信的部分节点,通过不断的ping/pong消息通信,经过一段时间后所有的节点都会知道集群全部节点的最新状态,从而达到集群状态同步的目的。

节点选择

Redis集群中的节点采用固定频率(定时任务每秒/10次)进行通信,因此,节点每次选择需要通信的节点列表变得非常重要,节点选择过多,虽然可以做到信息及时交换但是成本过高;节点选择过少,会降低集群内所有节点彼此信息交互频率,从而影响故障判定、新节点发现的速度。

通信节点选择规则,如图所示:

其中,消息交换成本主要由单位时间内选择发送消息的节点数量每个消息携带的数据量来决定。

1)选择发送消息的节点数量

按照Gossip协议规则,每个节点每秒会随机选取5个节点,并从中找出最久没有通信的节点发送ping消息。同时,节点中存在一个定时任务,以100ms/次的频率(定时任务)扫描本地节点列表,如果发现某个节点最近一次接收pong消息的时间大于cluster_node_timeout/2,则立刻发送ping消息,防止该节点信息长时间未更新。

根据以上规则,每个节点每秒需要发送ping消息的节点数量= 1+(num1+num2+...+num10),其中:

  • 1:每秒随机选取5个节点并找出最久没有通信的节点。
  • num1~10:最近一次收受pong消息的时间 > cluster_node_timeout/2的节点。

可以看出,参数cluster_node_timeout对ping消息发送的节点数量影响非常大。当带宽资源紧张时,可以适当调大该参数值,反之,过度调大该参数值会影响消息交换的频率,从而影响故障转移、槽信息更新、新节点发现的速度。

cluster-node-timeout 15000  // 默认为15000,即:15s

2)每个消息携带的数据量

Gossip协议消息由消息头+消息体组成,因此,每条ping消息的数据量由消息头和消息体决定,其中:

  • 消息头主要占用空间的字段为myslots[CLUSTER_SLOTS/8](占用2KB),这块空间占用相对固定。
  • 消息体会携带一点数量的其他节点信息用于信息交换,每条ping消息每次最多携带(节点数/10)个节点信息、最少携带3个节点信息进行通讯。

ping消息携带(节点数/10)个节点信息进行通信的原因:为了能够在下线检测时间(cluster_node_timeout * 2)内收到大部分集群节点发来的下线报告,期望值为:80%。

计算规则:N*4*2*1/10 = 80%。其中:

  • N:集群中节点个数。
  • 4*2:下线检测时间内收到的心跳包个数。即:最长经过cluster_node_timeout/2时间内,当前节点就会向其他节点发送一个ping包,收到该ping包的节点会向源节点回复pong包,因此,在cluster_node_timeout时间内,当前节点会收到目标节点发送的两个ping包以及目标节点回复的两个pong包(共4个心跳包)。即:cluster_node_timeout * 2时间内收到4*2=8个心跳包。
  • 1/10:包含下线节点信息的概率。

Gossip协议优缺点

优点:

  • 可扩展性(Scalable):Gossip协议是可扩展的,一般需要O(logN)轮就可以将信息传播到所有的节点,其中N代表节点的个数。允许节点的任意增加和减少,新增节点的数据最终会与其他节点一致。
  • 容错(Fault-tolerance):网络中任何节点的重启或者宕机都不会影响Gossip消息的传播,具有天然的分布式系统容错特性。
  • 健壮性(Robust):Gossip协议是去中心化的协议,集群中的所有节点都是对等的,没有特殊的节点,所以任何节点出现问题都不会阻止其他节点继续发送消息。任何节点都可以随时加入或离开,而不会影响系统的整体服务质量。
  • 最终一致性(Convergent consistency):Gossip协议实现信息指数级的快速传播,因此在有新信息需要传播时,消息可以快速地发送到全局节点,在有限的时间内能够做到所有节点都拥有最新的数据。

缺点:

  • 消息延迟:节点随机向少数几个节点发送消息,消息最终是通过多个轮次的散播而到达全网,不可避免的造成消息延迟。
  • 消息冗余:节点定期随机选择周围节点发送消息,而收到消息的节点也会重复该步骤,不可避免地引起同一节点消息多次接收,增加消息处理压力。

数据分布

Redis Cluster方案采用Slot(哈希槽)来处理数据和节点之间的映射关系,一个切片集群共有 16384个Slot,对于每个进入Redis的键值对,先将Key按照CRC16算法计算一个16bit的值,再用这个16bit的值对16384取模,得到0~16383范围内的模数,每个模数代表一个相应编号的Slot(即:CRC16(key)%16384)。

Redis Cluster集群中的每个主节点负责分摊这16384个槽中的部分数据(即:每个Slot都对应一个主节点)。

Slot分配方式有两种:自动分配和手动分配。

自动分配

使用cluster create命令创建集群时,Redis会自动将16384个Slot平均分布在集群的各个主节点中。如图所示:

图中:

  • 主节点A覆盖:0~5000。
  • 主节点B覆盖:5001~10000。
  • 主节点C覆盖:10001~16383。

手动分配

使用cluster meet命令手动建立节点间的连接,形成集群,再使用cluster addslots命令指定每个节点上分配的Slot。

操作示例:

# 将Redis节点加入集群
cluster meet 172.16.19.5 6379
# 为Redis节点分配Slot
redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1
redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3
redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4

注意:在手动分配Slot时,需要将16384个Slot分配完毕,否则Redis集群无法正常工作。

故障转移

Redis Cluster模式的故障转移不需要Sentinel系统辅助,而是通过集群内部主节点选举完成,是一个“自治”的系统。

故障转移大致分为三大步骤:故障检测、从节点选举以及故障转移。

故障检测

故障检测需要经历单节点检测、检测信息传播、下线判决三个步骤。

1)单节点检测

集群中的每个节点会通过集群内部通信总线(Cluster Bus)定期向其它节点发送Ping消息,检测对方是否在线。如果目标节点在规定的时间内(cluster_node_timeout)没有返回Pong消息,则该节点会被标注为疑似下线状态(Pfail:Probable Fail)。

2)检测信息传播

集群中的每个节点会通过相互发送消息的方式来交换自身保存的其他节点的状态信息(如:在线、疑似下线、下线等),当某个主节点通过消息获得其他主节点疑似下线的下线报告时,该主节点会将下线报告更新到自身保存的集群状态信息中。

3)下线判决

当超过半数的主节点将某个主节点标注为疑似下线时,该主节点就会被标记为下线并广播出去,所有收到这条广播消息的节点会将该主节点标记为下线。

主节点选举

主节点被标记为下线后,该主节点对应的从节点会发起投票,竞争升主。历经从节点拉票、主节点投票、投票裁决等环节后完成选举。

1)从节点拉票

当从节点发现自身对应的主节点的状态为已下线时,该从节点会将自身的currentEpoch(当前纪元)+1,并向集群广播一条请求消息(FAILOVER_AUTH_REQUEST),请求所有收到这条请求消息并具有投票权的主节点给自己投票(FAILOVER_AUTH_ACK)。

注意:从节点在发现其主节点下线时,并非立即发起故障转移流程进行拉票,而是需要等待一段时间,在未来的某个时间点才发起选举。计算公式为:

mstime() + 500ms + random()%500ms + rank*1000ms

其中:

  • 500ms:固定延时500ms,为某个主节点下线的消息传播到集群中的其他主节点预留时间,使得其他主节点可以参与投票。
  • random()%500ms:随机延时,为了避免两个从节点同时开始故障转移流程。
  • rank:从节点的排名(或优先级),指的是当前从节点在下线主节点的所有从节点中的排名,排名主要是根据复制数据量来定,复制数据量越多,排名越靠前。因此,复制数据量较多的从节点可以更早发起故障转移流程,从而优先成为新的主节点。

2)主节点投票

当一个拥有投票权且尚未给其他从节点投票的主节点收到某个从节点的投票请求时,该主节点会向该从节点返回一条响应消息,表示赞成该从节点升级为新的主节点。

3)投票裁决

当某个从节点收到的赞成票数大于N/2 + 1时,该从节点将升级为新的主节点,否则进入下一轮投票,直到选出新的主节点。

主节点选举基于Raft算法实现。

故障转移

选举完成后,获胜的从节点将发起故障转移(Failover),并接管原来主节点的Slots。

处理过程:

  • 1)被选举为主节点的从节点执行replicaof no one(Redis5.0以前为saveof no one) 命令脱离原主节点,使之升级为新的主节点。
  • 2)新的主节点撤销(clusterDelSlot操作)所有对已下线主节点的Slot指派,将这些Slot全部指派(clusterAddSlot操作)给自己。
  • 3)新的主节点会向集群中广播一条Pong消息,将自己升主的信息通知到集群中所有节点,完成故障转移。

新增主节点

Redis Cluster模式下新增主节点,主要步骤:

  • 新增主节点加入集群。
    • 1)向集群中新增主节点B,集群中某个主节点(节点A)先在自身的clusterState.nodes字典中为节点B创建一个clusterNode结构,再使用cluster meet <ip> <port>命令向节点B发送一条meet消息。
    • 2)节点B收到节点A发送的meet消息后,在自身的clusterState.nodes字典中为节点A创建一个clusterNode结构,并向节点A回复一条pong消息,确认节点B已经成功接收节点A发送的meet消息。
    • 3)节点A收到节点B回复的pong消息后,向节点B回复一条ping消息,确认节点A已经成功接收节点B返回的pong消息,完成握手。
    • 4)节点A通过Gossip协议将节点B的信息传播给集群中的其他节点,至此,节点B加入集群成功。
  • Slot迁移。新增主节点成功加入集群后,将集群中其他主节点的部分Slot迁移至新增主节点中。

客户端重定向

在Redis Cluster模式下,集群中的节点会通过Gossip协议将自身的Slot信息发给与它相连的其它节点,完成Slot分配信息的扩散。

客户端和集群中的节点建立连接后,节点会将Slot分配信息发给客户端,客户端收到Slot信息后,将Slot信息缓存在本地。客户端向Redis集群发送请求时,先在本地计算Key所对应的Slot,再向Slot对应的节点发送请求。

节点与Slot的对应关系可能发生变更,如:

  • 集群中新增或删除节点时,Redis重新分配部分Slot。
  • 为了实现集群负载均衡,将Slot重新分配给所有节点。

此时,客户端无法主动感知Slot分配信息的变更。当客户端向Redis集群发送请求时,Key对应的Slot可能不在当前节点中,需要通过重定向来将请求转发到正确的节点上。

重定向指的是Redis在接收到客户端请求时,如果Key对应的Slot位于当前节点中,则直接执行命令,否则不执行该命令,返回重定向信息。

MOVED

如图所示:

图中:

  • 节点-1中分配Slot-0、Slot-1,节点-2中分配Slot-2、Slot-3,节点-3中分配Slot-4。
  • 将Slot-3迁移到节点-3中。
  • 客户端向节点-2发送请求时,由于Key对应Slot-3已经被迁移到节点-3中,因此节点-2会向客户端返回一条MOVED报错信息(MOVED 5798 192.168.31.106:6381)。
  • 客户端根据返回的MOVED信息更新本地缓存,并向节点-3重新发送客户端请求。

当发生MOVED重定向时,客户端需要进行两次请求操作。因此,Redis客户端会在本地维护一份Slot和节点的对应关系,在执行指令前先计算当前Key存储的目标节点,再连接到目标节点进行数据操作。

MOVED示例:

127.0.0.1:6379> get name
(error) MOVED 5798 192.168.31.106:6381

ASK

以上示例中,如果在Slot-3从节点-2迁移到节点-3的过程中向节点-2发送客户端请求,则节点-2会向客户端返回一条ASK报错信息。如图所示:

  • 节点-1中分配Slot-0、Slot-1,节点-2中分配Slot-2、Slot-3,节点-3中分配Slot-4。
  • 将Slot-3迁移到节点-3中。
  • 客户端向节点-2发送请求时,由于Key对应Slot-3正在迁移中(图中绿色箭头所示),因此节点-2会向客户端返回一条ASK报错信息(ASK 5798 192.168.31.106:6381)。
  • 客户端根据返回的ASK信息向节点-3发送ASKING命令,然后再向节点-3重新发送客户端请求。注意:如果不先发送ASKING命令,直接向节点-3发送客户端请求,节点-3会拒绝执行客户端请求,并返回MOVED报错信息。

ASK命令有两层含义:

  • 1)表明Key对应的Slot数据正在迁移中。
  • 2)ASK命令向客户端返回请求数据所在的真实地址。

ASK示例:

127.0.0.1:6379> get name
(error) ASK 5798 192.168.31.106:6381
127.0.0.1:6379> ASKING
OK
127.0.0.1:6379> get name
"wuzp"

MOVED和ASK区别:

  • MOVED命令会更新客户端本地缓存,后续所有命令都发往新节点(即:Slot转移后的节点)。
  • ASK命令不会更新客户端本地缓存,后续所有命令仍发往旧节点(即:Slot转移前的节点)。

Cluster模式功能限制

1)Key批量操作支持有限,目前只支持具有相同Slot值的Key执行批量操作。

2)key事务操作支持有限,目前只支持多Key在同一个节点上的事务操作,当多个Key分布在不同的节点上时无法使用事务功能。

3)Key作为数据分区的最小粒度,不能将一个大的键值对象(如:hash、list等)映射到不同的节点。

4)不支持多数据空间,单机下的Redis可以支持16个数据库,集群模式下只能使用一个数据库空间(即:db0)。

5)主从同步只支持一层,从节点只能从主节点进行数据同步,不支持从从节点进行数据同步。

Cluster模式常用命令

cluster info:打印集群的信息。

cluster nodes:列出集群当前已知的所有节点以及这些节点的相关信息。

cluster meet <ip> <port>:将指定节点添加到集群中。

cluster forget <node_id>:从集群中移除指定节点。

cluster replicate <master_node_id>:将当前从节点设置为master_node_id指定的主节点的从节点(该命令只针对从节点)。

cluster saveconfig:将节点的配置文件保存到磁盘中。

cluster addslots <slot> [slot ...]:将一个或多个槽指派给当前节点。

cluster delslots <slot> [slot ...]:移除一个或多个槽对当前节点的指派。

cluster flushslots:移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。

cluster setslot <slot> node <node_id>:将槽 slot 指派给node_id指定的节点。

cluster setslot <slot> migrating <node_id>:将本节点的槽迁移到node_id指定的节点中。

cluster setslot <slot> importing <node_id>:从node_id指定的节点中导入槽到本节点。

cluster setslot <slot> stable:取消对槽的导入或者迁移。

cluster keyslot <key>:计算键key应该被放置在哪个槽上。

cluster countkeysinslot <slot>:返回槽目前包含的键值对数量。

cluster getkeysinslot <slot> <count>:返回count个slot槽中的键 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值