k8s 手动恢复redis 集群_Redis集群(终篇)——故障自动检测与自动恢复(附优质Redis资源汇总)...

e075130e07a04bfdf394fa76a9723d7b.png

前言

本篇是Redis集群的最后一篇文章,在Redis集群(上)中我们主要介绍了集群的数据分布策略、数据访问路由实现以及用于集群元数据管理的Gossip通信协议。在Redis集群(中)我们主要介绍了数据迁移为核心的集群伸缩过程。那么在最后的一篇中,我们着重来介绍集群故障的自动检测与恢复,即集群的高可用技术。整个系列的目录如下:

464aa03324bc54bf62da9712776e7fef.png
Redis集群系列目录

同时这一篇也是Redis系列的最后一篇文章,在文末我把这两个月研究Redis时所查阅到的优质资料进行了汇总分享给大家。在研究了这个系统两个月之后,我越发的觉得Redis是个极好的学习研究对象。首先它一点都不庞大,源码较少且清晰易懂,其次做为一个在业界流行使用的系统,整个系统的设计考虑了很多工业级的细节,吃透这个系统对理解其他的分布式存储系统有着极大的帮助。希望文末的资源能对大家有帮助~

故障恢复总体介绍

我们在之前提到过,Redis将所有的数据都分到了16384个slots里面同时每个节点负责一部分slots。slot和节点的对应关系是多对一的关系,即每个slot只能被至多一个节点负责存储,每个节点可以负责存储多个slots。此时如果集群中的某个Master节点因为故障下线,就会导致该Master节点负责的slots不能被继续提供服务,那么整个集群就下线(CLUSTER_FAIL)了,不然客户端请求下线Master节点上的slots内的数据时总会报错。所谓的高可用指的是,即使其中一个Master节点下线,整个集群依然能够正常向外提供服务。这是如何做到的呢?简单的来说就是让下线Master节点的Slave节点来成为新的Master节点,接管旧Master负责的所有slots向外提供服务。比如下面的集群拓扑结构,每个Master节点带一个Slave节点。如果M2永久下线之后,那么S2就会替代M2继续向外服务。那么如果替代的S2再次下线后会怎么样呢?显然由于S2不再有Slave节点了,所以S2下线之后整个集群就下线了。为了解决这个问题,Redis还提出一个叫 Replica Migration的解决方案:当集群中的某个Master节点没有Slave节点时(称之为 Orphaned Master),其他有富余Slave节点的主节点会向该节点迁移一个Slave节点以防该节点下线之后没有子节点来替换从而导致整个集群下线。

f08408434f90dcce4b3104217c6a4e18.png
Redis集群——每个Master节点带有一个Slave节点

所以我们的目标很简单:

  • 当集群中的一个Master节点故障之后,我们让该Master节点的子节点代替该Master节点继续向外提供即可。这个步骤我们叫slave promotion
  • 当集群中的一个Master节点成为Orphaned Master节点时,我们从其他有富余子节点的地方迁移过来一个子节点给这个孤立节点。

我们先讨论第一个问题。要解决第一个问题我们就得先解决如下的几个问题:

  1. 故障发现: 如何判定某个Master节点故障了?是管理员来决定?还是某个节点来决定?还是说集群中的节点都来参与投票决定? Redis采用了多数投票的方案
  2. 子节点选取:Master下线后其子节点要继位,当Master有多个Slave节点的时候该选择哪个Slave节点继位?如何去选?
  3. 配置更新:当slave promotion完成之后,那么之前的Master节点以及该节点的其他子节点该如何处置?Redis采用了让这些节点成为新的master节点的子节点的方案。

接下里的文章中,我们就依次介绍Redis对上述问题的解决方案。同时我们需要事先指出的是,Redis集群中只有Master节点具备参与节点状态判定和Slave节点选举的资格,同时Redis集群中的cluster_size指的也是Master节点的个数。

故障发现

PFAIL

和哨兵部分的故障发现一样,Redis集群的故障发现也经历两个阶段:PFail和Fail。PFAIL就是主观下线,比如节点1判定节点3下线,那么他会标记节点3的状态为PFAIL。但是如果绝大部分节点都判定节点3为PFAIL,那么我们就可以断定节点3故障下线,其状态判定为FAIL状态。就像你发现你自己打不开百度网页,于是你认为百度搜索崩了,你标记它为PFAIL。但这不代表百度真的崩了,也许只是因为你没联网。但是如果全国大部分人都打不开百度网页了,那么基本就可以说百度搜索是真的崩了,这时可以判定他的状态是FAIL的状态。

我们以如下的过程示例介绍故障发现的过程。

b83ed7b54b0c25df50e3382515bac080.png
PFail判定

Redis的每个节点会不停的向其他节点发送PING消息来与其他节点同步信息的同时检测其他节点是否可达。我们以节点1的视角为例去介绍这个过程。当节点1与节点3建立连接之后,会不定时的向节点3发送PING命令,每次发送PING命令时节点1会记录发送命令的时刻ping_sent,同时会至多等待node_timeout/2来获得节点3的回复。正常情况下,节点3接收到PING命令后会给节点1返回PONG回复,但是此时节点3故障了,那么节点1会发现在等待node_timeout/2之后还是没有得到节点3的回复。这时节点1就判定节点3故障么?不是的,节点1没有收到节点3的回复还有可能是他们之间本身连接出了问题,所以节点1会首先尝试与节点3重新建立连接。

while

由于此时节点3处于故障状态,那么节点1就会重新建立连接失败。此时节点1会记录连接建立失败的时刻,在实现中,这个时间也是记录到ping_sent变量中。

if 

当节点1发现与节点3的断连时间超过了node_timeout之后,就会标记节点3为PFAIL,即Possible failure,可以中文意为主观下线节点1标记节点3为PFAIL只是说明节点1认为节点3故障了,但并不代表节点3真正的故障了,因为或许是因为节点1和节点3之间的网络出了问题。

     while((de = dictNext(di)) != NULL) {
        clusterNode *node = dictGetVal(de);
        now = mstime(); /* Use an updated time at every iteration. */
        mstime_t delay;
        // 计算节点3断连时间
        delay = now - node->ping_sent;
        // 如果节点3断连时间超过cluster_node_timeout, 则标记节点3为PFAIL
        if (delay > server.cluster_node_timeout) {
                node->flags |= CLUSTER_NODE_PFAIL;
                update_state = 1;
            }
        }
    }

FAIL

当节点1标记节点3为PFAIL后,节点1会通过Gossip消息把这个信息发送给其他节点,接收到信息的节点会进行节点3客观下线状态判定。之前我们简单介绍过Redis的Gossip协议使用,节点1每次随机向其他几个节点发送自己视角下的部分节点状态信息。当节点2接收到来自节点1关于节点3的状态判定信息之后,节点2首先会把节点1加入到节点3的下线报告列表(Fail Report)中。每个节点都会维护一个下线报告列表,主要维护一个节点被哪些节点报告处于下线状态。比如节点4在节点1之前就向节点2报告了节点3的PFAIL信息,当节点2把节点1加入到节点3的下线报告后下线报告如下图所示。

3e2e46a387058e0ec21394b07fe63b24.png

节点2把节点1加入到下线报告后,会进行节点2客观下线状态(FAIL)的判定。

void 

所谓客观下线状态的判定规则是: 当集群中有超过1/2数目的节点都认为节点3处于PFAIL,那么就判定节点3为FAIL。同时需要指出的是,节点1把Gossip消息发送给其他节点后,只有同样认为节点3处于PFAIL状态的节点才会去做客观下线状态判定。由于节点2也判定节点3处于PFAIL,所以节点2进入客观下线的判定。当节点2发现有一半以上(包括自己)的主节点都报告节点3处在PFAIL状态时,节点2标记节点3为FAIL状态,并立刻向集群所有节点广播这个信息。

void 

广播信息

节点2判定节点3为FAIL状态后,向全集群的节点广播Node3的故障消息CLUSTERMSG_TYPE_FAIL。当集群中的节点收到此消息时,都会标记节点3的状态为FAIL状态,包括节点3的两个子节点S1,S2也会标记节点3为FAIL状态。

8aeadd4c6451b1414b9a5b79c9b823bc.png
S1、S2为节点3的子节点

至此,故障发现判定到此就介绍完了。整体过程为,首先每个节点都会自主判断其他节点状态,当一个节点A与自己断连时间过长,则判定该节点为PFAIL状态,并将A节点的判定状态Gossip给集群的其他节点。其他节点接收到消息后,会累计节点A的下线报告数,如果节点A的下线报告数目超过了cluster_size/2,即说明一半以上的节点都认为A下线,那么就判定A真正的下线,标记为FAIL。判定结束后,向集群广播节点A下线消息,其他节点都会更新自己维护的节点A的状态信息,标记A为FAIL。

当节点3故障后,我们要采用的故障恢复的方案就是让节点3的子节点代替节点3继续向外提供服务。那么节点3有两个子节点,到底该选择哪个子节点来替代呢?这就是我们接下来要介绍的故障迁移。


故障迁移

我们接着上面节点2向集群广播消息往下讲。当节点3的的两个子节点接收到其主节点的FAIL状态消息时,两个节点就会开始发起故障迁移,竞选成为新的Master节点。两个节点参与竞选之前,首先要检查自身是否有资格参与竞选。

资格检查

Slave节点会不停的与Master节点通信来复制Master节点的数据,如果一个Slave节点长时间不与Master节点通信,那么很可能意味着该Slave节点上的数据已经落后Master节点过多(因为Master节点再不停的更新数据但是Slave节点并没有随之更新)。Redis认为,当一个Slave节点过长时间不与Master节点通信,那么该节点就不具备参与竞选的资格。具体的判定如下:

/* 计算与Master节点上次通信过去的时间*/
    

休眠时间计算

当S1和S3都发现自己具备竞选资格时,就开始参与竞选。Redis子节点竞选成为新的Master节点采用了Raft协议。Raft协议选举过程中,所有参与选举的节点首先随机休眠一段时间,每个节点一旦唤醒就立刻向所有的投票节点发起拉票请求。对于投票节点来说,每一轮选举中只能投出一票,投票的规则就是先到先得。所以一般情况下,都是休眠时间最短的节点容易获得大部分投票。所以S1、S3也需要进行随机休眠,其每个节点的随即休眠时长计算公式如下。整个休眠时间由两部分组成:

  • 一部分为固定的500ms时间,这500ms主要是为了等待集群状态同步。上面我们讲到节点2会向集群所有节点广播消息,那么这500ms就是等待确保集群的所有节点都收到了消息并更新了状态。
  • 另一部分主要是一个随机的时间加上由该Slave节点的排名决定的附加时间。我们在之前主从复制的文章中讲过,每个slave都会记录自己从主节点同步数据的复制偏移量。复制偏移量越大,说明该节点与主节点数据保持的越一致。那么显然我们选举的时候肯定是想选状态更新最近的子节点,所以我们按照更新状态的排序来确定休眠时间的附加部分。状态更新最近的节点SLAVE_RANK排名为1,那么其休眠的时间相应的也最短,也就意味着该节点最有可能获得大部分选票。
DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds + SLAVE_RANK * 1000 milliseconds.

发起拉票&选举投票

我们假设S1先唤醒,S1唤醒后向所有节点发起拉票请求,即向其他节点发送CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST类型的消息。但是我们需要说明的是,虽然所有的节点(主节点+子节点)都会收到拉票请求,但是只有主节点才具备投票资格。当其他主节点接收到拉票请求时,如果这一轮投票过程中该主节点没有投出自己的票,那么就会把自己的票投给S1,即向S1回复FAILOVER_AUTH_ACK消息。当S1接收到来自其他节点的ACK消息时会统计自己获得的票数,当S1发现自己收到集群中一半以上的主节点的投票时就会开始执行failover,即替换自己的主节点过程。

// 发现自己获得了超过半数的集群节点的投票

替换节点

S1替换节点3的过程比较清晰易懂。即首先标记自己为主节点,然后将原来由节点3负责的slots标记为由自己负责,最后向整个集群广播现在自己是Master同时负责旧Master所有slots的信息。其他节点接收到该信息后会更新自己维护的S1的状态并标记S1为主节点,将节点3负责的slots的负责节点设置为S1节点。

void 

集群配置更新

那么最后我们需要解决的是,当S1成为了新的Master之后,S2和节点3该如何处理?显然并不是篡位之后就杀掉hh。实际上我们是让S2和节点3成为新的主节点S1的Slave节点,去备份S1节点的数据。那这个过程是如何进行的呢?这是在各个节点信息更新的时候自动实现的。当节点3故障恢复重新上线后,发现原先本该由自己负责的slot被S1负责了,那么他就知道自己被替代了,会自动成为S1节点的子节点,当S2节点发现原先应该由其Master节点3负责的slot被S1负责了,那么他就知道自己的Master被替代了,就会成为S1的Slave节点。

至此我们完成了整个故障迁移的内容介绍。总共分为如下几步:

  1. 子节点竞选资格检查
  2. 子节点休眠时间计算
  3. 子节点发起拉票,其他主节点投票
  4. 获得多数选票的子节点替换其主节点并向集群所有节点广播该信息
  5. 其他节点接收信息后更新配置
  6. 原先的主节点以及该主节点的其他节点自动成为新的主节点的Slave节点。

Redis优质资源汇总(持续更新)

这是我写的第九篇Redis文章,过去两个月研究的过程中阅读了不少的资料,也发现了不少好的Redis文章。就像前文提到,Redis是一个非常优质的研究对象,尤其适合学生党研究。因为本身系统mini,简单易懂,但是各个机制的设计又具备工业级的鲁棒性,所以非常适合研究学习。接下来按照顺序介绍:

Redis源码篇

Redis源码强推这个系列的博客,写的很不错,几乎涵盖了所有的Redis源码文件的分析。虽然这个博客里面的Redis源码是比较旧的版本了,但是绝大部分内容都是适用的。

https://blog.csdn.net/men_wen/article/details/75668345​blog.csdn.net

Redis入门篇:

Redis入门我推荐MANING出版社的《Redis in action》和我的第一篇博客。MANING出版社的质量读过的都是知道的。我的这一篇Redis文章的整体读者反馈还是不错的,在1700的阅读量当中,有27次收藏和20次文章点赞,我个人也认为写的不错,所以在这里推荐给大家。

《Redis实战 Redis in action Redis设计与实现入门指南 新华书店官方正版书籍》【摘要 书评 试读】- 京东图书​item.jd.com 大龙:Redis详解(1)——为什么我们都需要了解Redis​zhuanlan.zhihu.com
7c26ce71c315b9e0d82109ecf1375e94.png

Redis单机高性能篇

Redis后期的版本加入了多线程处理,但是其实单线程的性能已经相当的优秀。那么这里也推荐另一篇我的博客,该篇博客有近2000的阅读量,同时有60次收藏,读者反馈还是不错的。

大龙:Redis详解(2)——Redis是如何做到单线程服务10000客户端的​zhuanlan.zhihu.com
7c26ce71c315b9e0d82109ecf1375e94.png

Redis集群篇

Redis集群篇我首先推《Redis开发与运维》这本书,这本书整体的语言风格非常简洁,废话不多,内容充实而且插图丰富,作者由浅入深的介绍了Redis的相关内容。这本书我主要阅读了Redis主从复制、哨兵模式、集群相关内容。

《Redis开发与运维》(付磊,张益军)【摘要 书评 试读】- 京东图书​item.jd.com

再推Raft协议动画演示网站。哨兵模式和集群的节点选举中都用了Raft协议,所以对于Raft协议的理解将有助于你理解故障恢复中的节点选举过程。Raft协议本身不难,但是细节比较多,这个网站制作了很有意思的动画来展示Raft协议的选举,直观明了,清晰易懂。

Raft​thesecretlivesofdata.com

MIT 6.824课程助教也写了一个关于Raft得理解,写的很好:

https://thesquareplanet.com/blog/students-guide-to-raft/​thesquareplanet.com

同时关于Raft协议在Redis哨兵中的具体使用,可以参考由网易云Redis开发工程师写的这篇文章,写的非常详细。

Raft协议实战之Redis Sentinel的选举Leader源码解析​weizijun.cn
35e5e108eea61191170fddc124f80413.png

当然了,还有Redis官网的文档。我觉得Redis官网的介绍文档写的非常好,文章逻辑清晰,而且都是增量介绍。比如介绍Sentinel的时候,会首先告诉你系统设计设计目标,然后会告诉你Sentinel设计的核心点,不会让你淹没在细节当中。如果读英语问题不大的话,强烈建议读下官网的文档。我认为写的好的两篇:

Redis Cluster Specification - Redis​redis.io Redis Sentinel Documentation - Redis​redis.io

总结

Redis系列的文章就暂时到这里,以后如果有看到有意思的主题我会再继续写相关内容。分布式存储技术研究完了,接下来主要会研究分布式任务调度技术。我会以Mesos为例研究任务调度的系统设计和组件实现。如果感兴趣的话,欢迎关注我的专栏~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值