Redis面试系列:Redis Cluster如何通信?客户端怎么知道数据位于哪个节点?滴滴三面问到你怀疑人生(五)

前言

如何保证Redis的高并发和高可用?Redis的哨兵原理能介绍一下么?Redis Cluster如何通信?客户端怎么知道数据位于哪个节点?MOVED具体是怎么实现的?

一、Sentinel(哨兵)模式

Sentinel本质上只是一个运行在特殊模式下的Redis服务器

Sentinel(哨兵)是Redis的高可用性(high availability)解决方案:由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。

在这里插入图片描述

Sentinel模式其实就是主从的技术方案(可以实现读写分离)的基础上,加入了Sentinel系统来监视整个主从集群的健康情况,如果发现有主服务器宕机,Sentinel系统就会对宕机服务器执行故障转移操作:

  • Sentinel系统会挑选主服务属下的其中一个从服务器,并将这个被选中的从服务器升级为新的主服务器
  • Sentinel系统会向主服务属下的所有从服务器发送新的复制指令,让它们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕。
  • Sentinel系统还会继续监视已下线的服务,并在它重新上线时,将它设置为新的主服务器的从服务器。

在这里插入图片描述

启动一个Sentinel都做了哪些事情:

  1. 初始化服务器。
  2. 将普通Redis服务器使用的代码替换成Sentinel专用代码。
  3. 初始化Sentinel状态。
  4. 根据给定的配置文件,初始化Sentinel的监视主服务器列表。
  5. 创建连向主服务器的网络连接。

创建连向被监视主服务器的网络连接后,Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关的信息。

对于每个被Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接:

  • 一个是命令连接,这个连接专门用于向主从服务器发送命令,并接收命令回复。
  • 另一个是订阅连接,这个连接专门用于订阅主从服务器的__sentinel__:hello频道。

启动完成之后,Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的信息:

# Server  主服务器本身的信息
...
run_id:7611c59dc3a29aa6fa0609f841bb6a1019008a9c
...
# Replication 主服务器属下所有从服务器的信息
role:master
...
slave0:ip=127.0.0.1,port=11111,state=online,offset=43,lag=0
slave1:ip=127.0.0.1,port=22222,state=online,offset=43,lag=0
slave2:ip=127.0.0.1,port=33333,state=online,offset=43,lag=0
...
# Other sections
...

Sentinel还会创建连接到从服务器的命令连接和订阅连接。同时也会每十秒一次的向从服务器发送INFO命令,从服务器会返回以下信息:

# Server
...
run_id:32be0699dd27b410f7c90dada3a6fab17f97899f
...
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
slave_repl_offset:11887
slave_priority:100
# Other sections
...
  • 从服务器的运行ID run_id
  • 从服务器的角色role
  • 主服务器的IP地址master_host,以及主服务器的端口号master_port
  • 主从服务器的连接状态master_link_status
  • 从服务器的优先级slave_priority
  • 从服务器的复制偏移量slave_repl_offset

发送信息

PUBLISH sentinel:hello “<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>”

每隔两秒钟,每个哨兵都会向自己监控主服务器和从服务器对应的__sentinel__:hello
频道里发送一个消息(包括自己的host、ip和runid还有对这个master的监控配置)。

接收信息

SUBSCRIBE sentinel:hello

对于每个与Sentinel连接的服务器,Sentinel既通过命令连接向服务器的__sentinel__:hello频道发送信息,又通过服务器的__sentinel__:hello频道接收信息

Sentinel在连接主服务器或者从服务器时,会同时创建命令连接和订阅连接,但是在连接其他Sentinel时,却只会创建命令连接,而不创建订阅连接。这是因为Sentinel需要通过接收主服务器或者从服务器发来的频道信息来发现未知的新Sentinel,所以才需要建立订阅连接,而相互已知的Sentinel只要使用命令连接来进行通信就足够了。

sdown和odown转换机制
sdown,即主观宕机,如果一个Sentinel它自己觉得master宕机了,就是主观宕机。
odown,即客观宕机,如果quorum数量的哨兵都认为一个master宕机了,则为客观宕机。

Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他Sentinel在内)发送PING命令,并通过实例返回的PING命令回复来判断实例是否在线。

哨兵在ping一个主服务器的时候,如果超过了is-master-down-after-milliseconds指定的毫秒数之后,就是达到了sdown,就主观认为主服务器宕机了。

如果一个Sentinel在指定时间内,收到了quorum指定数量的其他Sentinel也认为那个主服务器是sdown了,那么就认为是odown了,客观认为主服务器宕机,就完成了sdown到odown的转换。

当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器执行故障转移操作。

从服务器(slave)到主服务器(master)选举算法

主要通过下面几个步骤:

  1. 删除列表中所有处于下线或者断线状态的从服务器,这可以保证列表中剩余的从服务器都是正常在线的。
  2. 删除列表中所有最近五秒内没有回复过领头Sentinel的INFO命令的从服务器,这可以保证列表中剩余的从服务器都是最近成功进行过通信的。
  3. 删除所有与已下线主服务器连接断开超过down-after-milliseconds10毫秒的从服务器:down-after-milliseconds选项指定了判断主服务器下线所需的时间,而删除断开时长超过down-after-milliseconds10毫秒的从服务器,则可以保证列表中剩余的从服务器都没有过早地与主服务器断开连接,换句话说,列表中剩余的从服务器保存的数据都是比较新的。

接下来会对slave进行排序

  1. 按照slave优先级进行排序,slave priority越低,优先级就越高。
  2. 如果slave priority相同,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高。
  3. 如果上面两个条件都相同,那么选择一个run id比较小的那个slave

哨兵集群至少要 3 个节点,来确保自己的健壮性。

如果哨兵集群 是2 个节点的问题:
2个哨兵的majority是2(3个的majority=2,4个的majority=2,5个的majority=3)
只有2个节点,其中一个宕机,那么哨兵就只有一个了,此时就无法来通过majority来进行故障转移。

redis主从 + Sentinel的架构,不是保证数据的零丢失的,它是为了保证Redis集群的高可用。

Sentinel架构的缺点是什么?

二、Cluster(集群)模式

https://redis.io/topics/cluster-tutorial/ 建议阅读官方文档

Redis集群是Redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能。

一个Redis集群由多个节点组成。

可以这样理解一下Redis集群模式:
在这里插入图片描述
一组主从就是一个节点,把各个独立的节点连接起来就构成了一个集群。

Redis集群数据分片

Redis Cluster不使用一致的哈希,而是使用一种不同形式的分片:槽(slot)。
Redis集群中有16384个哈希槽,每个键都是属于这16384个槽中的其中一个,要计算一个键属于哪个哈希槽,只需对key的CRC16取模16384。

Redis群集中的每个节点都负责哈希槽的子集,如果有一个包含3个节点的群集,其中:
节点A包含从0到5500的哈希槽。
节点B包含从5501到11000的哈希槽。
节点C包含从11001到16383的哈希槽。

一个节点可以随意分配槽点,不是必须连续的。
为什么是16384个?

传播节点的槽信息

一个节点除了保存自己负责处理的槽信息,还会将自己的slots数组通过消息发送给集群中的其他节点,来告诉其他节点自己目前负责处理哪里槽。

这个很重要记一下,后面会用到。

每个节点都保存了不同的数据,客户端查询的时候,怎么知道去哪个节点找key呢?key换节点了怎么处理的?

在集群中执行命令逻辑:

在这里插入图片描述

MOVED错误

在节点发现key所在的槽不是自己负责处理的时候,就会返回一个MOVED错误:

MOVED <slot> <ip>:<port>

让客户端重定向( redirected)到正确的 Redis 节点。

因为节点本身会存储一份其他节点负责的槽点,所以MOVED很容易实现。

重新分片

Redis集群的重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点。
重新分片操作可以在线(online)进行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。

ASK错误

在进行重新分片期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。

当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时,怎么处理?

  1. 源节点会先在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令。
  2. 相反地,如果源节点没能在自己的数据库里面找到指定的键,那么这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令。

MOVED和ASK的区别

  • MOVED错误代表槽的负责权已经从一个节点转移到了另一个节点:在客户端收到关于槽i的MOVED错误之后,客户端每次遇到关于槽i的命令请求时,都可以直接将命令请求发送至MOVED错误所指向的节点,因为该节点就是目前负责槽i的节点。

  • 与此相反,ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施:在客户端收到关于槽i的ASK错误之后,客户端只会在接下来的一次命令请求中将关于槽i的命令请求发送至ASK错误所指示的节点,但这种转向不会对客户端今后发送关于槽i的命令请求产生任何影响,客户端仍然会将关于槽i的命令请求发送至目前负责处理槽i的节点,除非ASK错误再次出现。

节点间的内部通信机制

集群中的各个节点通过发送和接收消息(message)来进行通信,采有gossip协议。

gossip协议原理:在一个有界网络中,每个节点都随机地与其他节点通信,经过一番杂乱无章的通信,最终所有节点的状态都会达成一致。每个节点可能知道所有其他节点,也可能仅知道几个邻居节点,只要这些节可以通过网络连通,最终他们的状态都是一致的,很像疫情传播。
优点在于元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;
缺点在于元数据更新有延时可能导致集群的一些操作会有一些滞后。

节点发送的消息主要有以下五种:

  1. MEET消息:当发送者接到客户端发送的CLUSTER MEET命令时,发送者会向接收者发送MEET消息,请求接收者加入到发送者当前所处的集群里面。

  2. PING消息:集群里的每个节点默认每隔一秒钟就会从已知节点列表中随机选出五个节点,然后对这五个节点中最长时间没有发送过PING消息的节点发送PING消息,以此来检测被选中的节点是否在线。除此之外,如果节点A最后一次收到节点B发送的PONG消息的时间,距离当前时间已经超过了节点A的cluster-node-timeout选项设置时长的一半,那么节点A也会向节点B发送PING消息,这可以防止节点A因为长时间没有随机选中节点B作为PING消息的发送对象而导致对节点B的信息更新滞后。

  3. PONG消息:当接收者收到发送者发来的MEET消息或者PING消息时,为了向发送者确认这条MEET消息或者PING消息已到达,接收者会向发送者返回一条PONG消息。另外,一个节点也可以通过向集群广播自己的PONG消息来让集群中的其他节点立即刷新关于这个节点的认识,例如当一次故障转移操作成功执行之后,新的主节点会向集群广播一条PONG消息,以此来让集群中的其他节点立即知道这个节点已经变成了主节点,并且接管了已下线节点负责的槽。

  4. FAIL消息:当一个主节点A判断另一个主节点B已经进入FAIL状态时,节点A会向集群广播一条关于节点B的FAIL消息,所有收到这条消息的节点都会立即将节点B标记为已下线。

  5. PUBLISH消息:当节点接收到一个PUBLISH命令时,节点会执行这个命令,并向集群广播一条PUBLISH消息,所有接收到这条PUBLISH消息的节点都会执行相同的PUBLISH命令。

主备切换原理

1.判断节点宕机

集群中的每个节点都会定期地向集群中的其他节点发送PING消息,以此来检测对方是否在线,如果接收PING消息的节点没有在规定的时间内,向发送PING消息的节点返回PONG消息,那么发送PING消息的节点就会将接收PING消息的节点标记为疑似下线(probable fail,PFAIL)。

如果在一个集群里面,半数以上负责处理槽的主节点都将某个主节点x报告为疑似下线,那么这个主节点x将被标记为已下线(FAIL),将主节点x标记为已下线的节点会向集群广播一条关于主节点x的FAIL消息,所有收到这条FAIL消息的节点都会立即将主节点x标记为已下线。

2.选举新的主节点

  • 集群的配置纪元是一个自增计数器,它的初始值为0。
  • 当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会被增一。
  • 对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将获得主节点的投票。
  • 当从节点发现自己正在复制的主节点进入已下线状态时,从节点会向集群广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票。
  • 如果一个主节点具有投票权(它正在负责处理槽),并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持从节点成为新的主节点。
  • 每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根据自己收到了多少条这种消息来统计自己获得了多少主节点的支持。
  • 如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N/2+1张支持票时,这个从节点就会当选为新的主节点。
  • 因为在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所以如果有N个主节点进行投票,那么具有大于等于N/2+1张支持票的从节点只会有一个,这确保了新的主节点只会有一个。
  • 如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。

故障转移

当一个从节点发现自己正在复制的主节点进入了已下线状态时,从节点将开始对下线主节点进行故障转移,以下是故障转移的执行步骤:

  • 复制下线主节点的所有从节点里面,会有一个从节点被选中;
  • 被选中的从节点会执行SLAVEOF no one命令,成为新的主节点;
  • 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己;
  • 新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽;
  • 新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成。

总结

Redis的集群就这些内容了。一般来说小公司使用Sentinel就够了,大公司都会使用Cluster模式。
了解完这个你应该对怎么设计一个10亿数据的读写的Redis架构有个概念。
细节的东西还是非常多的,也需要大家多花点时间去看书学习的。

猜你喜欢

转载自blog.csdn.net/Oooo_mumuxi/article/details/106063207
今日推荐