面试必问的 Redis:高可用解决方案哨兵、集群,Java攒了一个月的面试题及解答

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

5、客户端与 Redis 节点直连,不需要中间代理层(proxy)。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

集群的架构图如下所示:

Redis 集群目标

1、高性能:没有代理层(proxy),采用异步复制,不对值进行合并操作。

2、水平扩展:集群共有 16384 个槽,每个节点负责处理部分槽,可以支持线性扩展至 1000 个节点。

3、写安全性:系统尝试(尽最大努力)保留来自与大多数主节点连接的客户机的所有写操作。但是由于使用异步复制,所以可能会丢失一些写命令。

4、可用性:集群模式下,Redis 能够自动进行故障检测、master 选举、故障转移。

MOVED错误

通过上面的介绍和架构图,我们知道客户端只会连接到某个节点上,但是该节点只负责部分槽,万一客户端请求的是其他槽怎么办?

Redis 引入 MOVED 错误来解决这个问题。

当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出要处理的数据库键属于哪个槽,并检查这个槽是否是自己负责的。

如果键所在的槽正好是自己负责,那么节点直接执行这个命令。

否则,节点会向客户端返回一个 MOVED 错误,该命令可以指引客户端转向(redirect)正确的节点,并再次发送之前想要执行的命令,得到正确的结果。

集群的主从复制

集群的每个分片中使用了主从复制来保证高可用,这边的主从复制逻辑也是直接复用的之前的主从复制模式的逻辑。

纪元(epoch)

Redis 集群中使用了类似于 Raft 算法 term(任期)的概念称为 epoch(纪元),用来给事件增加版本号。

Redis 集群中的纪元主要是两种:currentEpoch 和 configEpoch。

currentEpoch

集群当前的配置纪元,这是一个集群状态相关的概念,可以当做记录集群状态变更的递增版本号。

currentEpoch 作用在于,当集群的状态发生改变,某个节点为了执行一些动作需要寻求其他节点的同意时,就会增加 currentEpoch 的值,例如故障转移流程。

当从节点 A 发现其所属的主节点下线时,就会试图发起故障转移流程。首先就是增加 currentEpoch 的值,这个增加后的 currentEpoch 是所有集群节点中最大的。然后从节点A向所有节点发包用于拉票,请求其他主节点投票给自己,使自己能成为新的主节点。

其他节点收到包后,发现发送者的 currentEpoch 比自己的 currentEpoch 大,就会更新自己的 currentEpoch,并在尚未投票的情况下,投票给从节点 A,表示同意使其成为新的主节点。

configEpoch

节点当前的配置纪元,这是一个集群节点配置相关的概念,每个集群节点都有自己独一无二的 configepoch。所谓的节点配置,实际上是指节点所负责的槽位信息。

每一个 master 在向其他节点发送消息时,都会附带其 configEpoch 信息,以及一份表示它所负责的 slots 信息。

节点收到消息之后,就会根据消息中的 configEpoch 和负责的 slots 信息,记录到相应节点属性中。这边有两种情况:

1)如果该消息中的 slots 在当前节点中被记录为还未有节点负责,那可以直接指定为发送消息的节点。

2)如果消息中的 slots 在当前节点已经被记录为有节点负责,这种情况相当于有多个节点都宣称他负责了某个 slot,那怎么处理了?

这时候就要用到 configEpoch,configEpoch 更大的说明是更新的配置,当前节点会使用 configEpoch 更大的配置。

多个节点宣称负责同一个 slot 最常见的场景就是故障转移之后。当故障的主节点重新连接时,他会向集群其他节点发送消息,会带上自己故障前负责的 slots 信息,当其他节点收到后判断该节点的 configEpoch 更小,知道是旧的配置信息,则不会进行更新。

节点的 configEpoch 会在自己当选为新的主节点的时候,更新为集群当前选举的纪元,其实也就是 currentEpoch 的值。

因为每一次选举只会有一个从节点当选为新的主节点,所以该从节点的 configEpoch 会是当前所有集群节点 configEpoch 中的最大值。这样,该从节点成为主节点后,就会向所有节点发送广播包,强制其他节点更新相关槽位的负责节点为自己。

集群故障检测

本节与哨兵的故障核心思想是相同的。

集群中的每个节点都会定期地向集群中的其他节点发送 PING 消息,以此来检测对方是否在线。

集群节点间互相发送 PING 检测的时机,目前看主要有以下两个:

1、每秒执行1次:随机检查5个节点,选出最早收到 PONG 回复的节点,也就是最久没有通信过的节点,发送 PING 消息。

2、每100毫秒执行1次:轮询集群的节点,对于那些链接正常的节点,如果上一次收到该节点的 PONG 回复时间距离现在已经超过集群超时时间的一半(server.cluster_node_timeout/2),则直接向该节点发送 PING。

如果接收 PING 消息的节点在规定的时间内(cluster_node_timeout,默认15秒),没有向发送 PING 消息的节点返回 PONG 回复或者发送其他任何消息,那么发送 PING 消息的节点就会将接收 PING 消息的节点标记为疑似下线(probable failure,PFAIL)。

这边 Redis 没有将 PONG 回复作为目标节点存活的唯一证明,而是将目标节点的任何消息都作为存活的证明。这是因为在集群负载较高的时候,收到 PONG 回复可能会出现延迟。

集群中的各个节点在向其他节点发送 PING(PONG、MEET)消息的时候,会附加上 Gossip(八卦)消息,Gossip 消息记录了集群中其他节点的状态信息,例如某个节点是处于在线状态、疑似下线状态(PFAIL),还是已下线状态(FAIL)。

Gossip 消息包含两部分:

1)正常节点的状态信息:随机选择集群节点数的 1/10,但是不能小于3,除非目标集群节点中正常的数量已经小于3。

2)被标记为 PFAIL 的节点信息会被全部添加到 Gossip 消息中。

当主节点 A 通过 Gossip 消息得知主节点 B 认为主节点 C 进入了 PFAIL 或 FAIL 状态时,主节点 A 会在自己的 clusterState.nodes 字典中找到主节点 C 对应的 clusterNode 结构,并将主节点 C 的故障报告(failure report)添加到 clusterNode 结构的 fail_reports 链表中。

这样,主节点 A 就可以通过主节点 C 的 clusterNode->fail_reports 链表快速计算出有多少个节点将主节点 C 标记为 PFAIL 状态。

当主节点 A 为主节点 C 新增故障报告的时候,会顺带检查是否需要将主节点 C 标记为 FAIL,如果通过 fail_reports 链表发现主节点 C 被半数以上负责处理槽的主节点标记为疑似下线(PFAIL),则会进一步将主节点 C 标记为已下线(FAIL),同时向集群广播 “主节点 C 已经 FAIL 的消息”,所有收到消息的节点都会立即将主节点 C 标记为已下线。

集群故障转移

当 slave 发现自己正在复制的 master 进入了已下线(FAIL)状态,slave 会对下线的 master 进行故障转移,以下是故障转移的执行步骤:

1、发起一次选举,该下线的 master 的所有 slave 里面,会有一个 slave 被选中。

2、被选中的 slave 会升级为新的 master,清除 slave 相关的信息:slave 标记位等。

3、新的 master 会撤销所有对已下线 master 的槽指派,并将这些槽全部指派给自己。

4、新的 master 向集群广播一条 PONG 消息,这条 PONG 消息可以让集群中的其他节点立即知道这个节点已经由 slave 变成了 master ,并且这个新的 master 已经接管了原本由已下线节点负责处理的槽。集群中的其他节点收到消息后会更新自己保存的相关配置信息。

5、新的 master 开始接收和自己负责处理的槽有关的命令请求,故障转移完成。

集群选举

故障转移的第一步就是选举出新的主节点,以下是集群选举新的主节点的方法:

1、当从节点发现自己正在复制的主节点进入已下线状态时,会发起一次选举:将 currentEpoch 加1,然后向集群广播一条 CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票。

2、其他节点收到消息后,会判断是否要给发送消息的节点投票,判断流程如下:

  • 当前节点是 slave,或者当前节点是 master,但是不负责处理槽,则当前节点没有投票权,直接返回。

  • 请求节点的 currentEpoch 小于当前节点的 currentEpoch,校验失败返回。因为发送者的状态与当前集群状态不一致,可能是长时间下线的节点刚刚上线,这种情况下,直接返回即可。

  • 当前节点在该 currentEpoch 已经投过票,校验失败返回。

  • 请求节点是 master,校验失败返回。

  • 请求节点的 master 为空,校验失败返回。

  • 请求节点的 master 没有故障,并且不是手动故障转移,校验失败返回。因为手动故障转移是可以在 master 正常的情况下直接发起的。

  • 上一次为该master的投票时间,在cluster_node_timeout的2倍范围内,校验失败返回。这个用于使获胜从节点有时间将其成为新主节点的消息通知给其他从节点,从而避免另一个从节点发起新一轮选举又进行一次没必要的故障转移

  • 请求节点宣称要负责的槽位,是否比之前负责这些槽位的节点,具有相等或更大的 configEpoch,如果不是,校验失败返回。

如果通过以上所有校验,那么主节点将向要求投票的从节点返回一条 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 消息,表示这个主节点支持从节点成为新的主节点。

3、每个参与选举的从节点都会接收 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 消息,并根据自己收到了多少条这种消息来统计自己获得了多少个主节点的支持。

4、如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N/2+1 张支持票时,这个从节点就会当选为新的主节点。因为在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所以如果有 N个主节点进行投票,那么具有大于等于 N/2+1 张支持票的从节点只会有一个,这确保了新的主节点只会有一个。

5、如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。

这个选举新主节点的方法和选举领头 Sentinel 的方法非常相似,因为两者都是基于 Raft 算法的领头选举(leader election)方法来实现的。

如何保证集群在线扩容的安全性?

例如:集群已经对外提供服务,原来有3分片,准备新增2个分片,怎么在不下线的情况下,无损的从原有的3个分片指派若干个槽给这2个分片?

Redis 使用了 ASK 错误来保证在线扩容的安全性。

在槽的迁移过程中若有客户端访问,依旧先访问源节点,源节点会先在自己的数据库里面査找指定的键,如果找到的话,就直接执行客户端发送的命令。

如果没找到,说明该键可能已经被迁移到目标节点了,源节点将向客户端返回一个 ASK 错误,该错误会指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令,从而获取到结果。

ASK错误

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

当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时。源节点会先在自己的数据库里面査找指定的键,如果找到的话,就直接执行客户端发送的命令。

否则,这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个 ASK 错误,指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令,从而获取到结果。

MOVED和ASK的区别

从上面的介绍来看 MOVED 错误和 ASK 错误非常类似,都起到重定向客户端的效果,他们有什么区别?能否合并成一个?

MOVED 错误代表槽位的负责权已经从一个节点转移到了另一个节点:在客户端收到关于槽位 k 的MOVED 错误之后,会更新槽位 k 及其负责节点的对应关系,这样下次遇到关于槽位 k 的命令请求时,就可以直接将命令请求发送新的负责节点。

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

总结就是:

1)ASK 是一种迁移槽临时措施,只是会产生一次重定向

2)MOVED 代表该槽已经完全由另一个节点负责了,会触发客户端刷新本地路由表,之后对于该槽的请求都会请求新的节点。

这边提到的本地路由表是该集群的插槽和负责处理该槽的节点地址的映射,通过该路由表客户端可以在大部分情况下都直接请求到正确的节点,而无需重定向,从而提升性能。

数据丢失场景:脑裂(split-brain)

脑裂是导致 Redis 产生数据丢失比较常见的场景。如下例子:

集群有3个节点,每个节点采用1主2从,如下图所示,红色圈代表出现了网络分区故障。

当主节点 A 与集群中其他节点出现网络分区故障时,此时集群会分为2个分区,“少数派”:节点 A;多数派:节点 A1、A2、B、B1、B2、C、C1、C2。

由于集群节点之间的故障检测需要一定时间,通常是 cluster_node_timeout,因此在 cluster_node_timeout 期间内 Client1 仍然可以向节点 A 发出写命令,但此时由于网络分区节点 A 已经无法通过异步复制将命令传播到节点 A1 和 A2。

如果节点 A 在 cluster_node_timeout 内仍然无法恢复,则集群 “多数派” 这边会发起故障转移,选择 A1 和 A2 中的一个升级为新的 master,对外提供服务。

与此同时,节点 A 所在的 “少数派” 由于在 cluster_node_timeout 内无法检测到与其他节点的心跳,此时也会开始拒绝对外提供服务。

当网络分区故障恢复后,由于新 master 拥有更高的配置纪元,此时节点 A 会被降级为 slave,清空自身数据,然后复制新的 master。此时,节点 A 在网络分区故障期间处理的写命令就全部丢失了。

最后

==========

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

image.png

image.png

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

[外链图片转存中…(img-xR9Bv3y2-1713561835434)]

[外链图片转存中…(img-0ujNC6Up-1713561835435)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-2lEx7j61-1713561835436)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值