星空网站建设

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 6|回复: 0

聊一聊:聊聊 Redis 哨兵选举与故障转移的现

[复制链接]
  • TA的每日心情
    奋斗
    1 小时前
  • 签到天数: 62 天

    [LV.6]常住居民II

    2万

    主题

    56

    回帖

    7万

    积分

    超级版主

    Rank: 8Rank: 8

    积分
    77129
    发表于 前天 16:19 | 显示全部楼层 |阅读模式
    上一篇文章我们将哨兵主观下线的核心流程都分析完成,这一篇我们将接着之前的思路,将哨兵获取客观下线结果并结合raft协议完成哨兵leader选举完成故障转移的流程分析完成,希望对你有帮助。业内专家曾表示,悦数信息科技有限公司的发展壮大是很有可能的,从其以往的数据报表可以很好的看出来。悦数图数据库是一款完全自主研发的国产图数据库和原生分布式图数据库,具有高性能,易扩展,安全稳定,自主可控的特点.万亿级数据仅需毫秒级查询延时,应用于金融风控,实时推荐,知识图谱等业务场景。https://www.yueshu.com.cn/





    详解哨兵选举与故障转移流程
    1. 获取客观下线结果判断
    当前哨兵主观认定master下线之后,为了明确知晓master节点是否真的下线,哨兵节点还会通过cc即异步命令指针所维护的socket连接发起is-master-down-by-addr的sentinel指令进行询问,其他哨兵所回复的结果都会通过回调函数sentinelReceiveIsMasterDownReply函数处理。

    这段请求比较终会被其他哨兵sentinel命令所对应的函数sentinelCommand执行,他们各自会在内部查看自己对于master判断是否是主观下线,如果是则返回1。

    比较后我们的哨兵收到这个结果1,则通过位运算加master节点状态flags类加上客观下线的判断标识64,这里redis为了提升运算效率,采用的二进制|=运算,这一点我们在阅读大量的redis中源码都会看到二进制运算这一点化:



    对此我们也给出哨兵处理每一个master例的函数入口,可以看到在调用sentinelCheckSubjectivelyDown完成主观下线的检查之后,又会调用sentinelAskMasterStateToOtherSentinels并传入SENTINEL_NO_FLAGS即仅仅检查其他哨兵对于当前master的主观判断结果:

    复制
    //这个入参包含恰哨兵例和当前主节点的从节点信息
    void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
       //......
        //3. 主观判断是否下线
        sentinelCheckSubjectivelyDown(ri);

       //......

        /* Only masters */
        if (ri->flags & SRI_MASTER) {
          
           //......
            //传入master信息ri以及标识SENTINEL_NO_FLAGS意味仅了解其他哨兵对于master节点状态的判断
            sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
        }
    }
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12.
    13.
    14.
    15.
    16.
    步入sentinelAskMasterStateToOtherSentinels即可看到哨兵询问其他哨兵对于master判断的逻辑,可以看到它遍历出每一个哨兵例,通过异步连接cc指针所指向的连接发起SENTINEL is-master-down-by-addr指令获取其他哨兵节点对于master下线的看法,并注册sentinelReceiveIsMasterDownReply函数处理返回结果:

    复制
    #define SENTINEL_ASK_FORCED (1<<0)
    void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int flags) {
        dictIterator *di;
        dictEntry *de;

        di = dictGetIterator(master->sentinels);
        //遍历哨兵例
        while((de = dictNext(di)) != NULL) {
            sentinelRedisInstance *ri = dictGetVal(de);
            //......

            /* Ask */
            ll2string(port,sizeof(port),master->addr->port);
            //发送is-master-down-by-addr命令获取其他哨兵客观下线的结果,并通过sentinelReceiveIsMasterDownReply作为回调处理接收结果
            retval = redisAsyncCommand(ri->cc,
                        sentinelReceiveIsMasterDownReply, NULL,
                        "SENTINEL is-master-down-by-addr %s %s %llu %s",
                        master->addr->ip, port,
                        sentinel.current_epoch,
                        //若大于SENTINEL_FAILOVER_STATE_NONE则说明执行故障切换,传入server.runid  
                        (master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
                        server.runid : "*");
            if (retval == REDIS_OK) ri->pending_commands++;
        }
        dictReleaseIterator(di);
    }
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12.
    13.
    14.
    15.
    16.
    17.
    18.
    19.
    20.
    21.
    22.
    23.
    24.
    25.
    26.
    其他哨兵收到sentinel指令后就会调用sentinelCommand处理这条指令,其内部会判断自己所维护的master的flags二进制位是否包含SRI_S_DOWN,如果是则说明被请求的哨兵节点同样认为master已下线,则直接回复master的leaderid以及shared.cone即1(代表确认当前master确下线):

    复制
    void sentinelCommand(redisClient *c) {
        //......
        else if (!strcasecmp(c->argv[1]->ptr,"is-master-down-by-addr")) {//处理客观下线请求
            //......
          

          
            //如果master主观判定下线即flags包含SRI_S_DOWN这个主观下线标识,则isdown设置为1
            if (!sentinel.tilt && ri && (ri->flags & SRI_S_DOWN) &&
                                        (ri->flags & SRI_MASTER))
                isdown = 1;

            //上文isdown 设置为1,返回 shared.cone告知对应leaderid的master被我方认定为下线
            //响应3部分内容,下线状态、leader id以及当前leader的纪元
            addReplyMultiBulkLen(c,3);
            addReply(c, isdown ? shared.cone : shared.czero);
            addReplyBulkCString(c, leader ? leader : "*");
            addReplyLongLong(c, (long long)leader_epoch);
            if (leader) sdsfree(leader);
        } //......
        return;
    //......
    }
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12.
    13.
    14.
    15.
    16.
    17.
    18.
    19.
    20.
    21.
    22.
    23.
    比较终我们的sentinel的回调函数sentinelReceiveIsMasterDownReply处理对端的结果,发现返回值为1,说明该节点对于我们的来说客观认为master下线了。

    所以我们的哨兵就需要记录这个消息,因为我们维护master->sentinels的字典记录其他哨兵信息,所以定位到其他哨兵客观下线的回复后,我们就会从这个字典中找到这个哨兵的结构体将其flags累加一个SRI_MASTER_DOWN的常数值64,意味这个哨兵客观认定这个master下线了:

    复制
    void sentinelReceiveIsMasterDownReply(redisAsyncContext *c, void *reply, void *privdata) {
      //......


        if ( //......)
        {
        //更新上次响应时间
            ri->last_master_down_reply_time = mstime();
            if (r->element[0]->integer == 1) {//如果返回(cone默认设置为1)1则说明其他哨兵认为master下线,累加将当前维护的哨兵字段的flags累加SRI_MASTER_DOWN
                ri->flags |= SRI_MASTER_DOWN;
            } else {
               //......
            }
            //......
        }
    }
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12.
    13.
    14.
    15.
    16.
    2. 启动故障转移
    上一步收集其他哨兵的判断并更新到各自的flags位后,当前哨兵的定时任务再次遍历master调用sentinelHandleRedisInstance处理当前master,其内部会遍历当前哨兵维护的哨兵数组获取这些哨兵对于master下线的看法,如果累加到的哨兵对于下线的看法大于或者等于我们配置quorum之后,则会判定会客观下线:



    我们还是从sentinelHandleRedisInstance方法查看方法入口,可以看到哨兵定时执行该方法时会调用sentinelCheckObjectivelyDown检查客观下线状态:

    复制
    void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
       //......
        if (ri->flags & SRI_MASTER) {
           //......
           //检查其当前是否客观下线
            sentinelCheckObjectivelyDown(ri);
            //......
        }
    }
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    步入其内部即可看到笔者所说的,遍历哨兵查看下线结果并更新master下线状态的逻辑:

    复制
    void sentinelCheckObjectivelyDown(sentinelRedisInstance *master) {
       //......
        //如果是主观下线,步入该逻辑
        if (master->flags & SRI_S_DOWN) {
            //自己的票数设置进去,quorum为1
            quorum = 1; /* the current sentinel. */
          
          
            //遍历其他哨兵,如果为客观下线则累加quorum
            di = dictGetIterator(master->sentinels);
            while((de = dictNext(di)) != NULL) {
                sentinelRedisInstance *ri = dictGetVal(de);

                if (ri->flags & SRI_MASTER_DOWN) quorum++;
            }
           //如果投票数大于配置的quorum,则odown 为1,即说明客观认定下线了
          
            if (quorum >= master->quorum) odown = 1;
        }

        //如果明确客观下线,则广播+odown事件
        if (odown) {
            if ((master->flags & SRI_O_DOWN) == 0) {
                sentinelEvent(REDIS_WARNING,"+odown",master,"%@ #quorum %d/%d",
                    quorum, master->quorum);
             //累加标识,并更新master下线时间
                master->flags |= SRI_O_DOWN;
                master->o_down_since_time = mstime();
            }
        } else {
           //......
        }
    }
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12.
    13.
    14.
    15.
    16.
    17.
    18.
    19.
    20.
    21.
    22.
    23.
    24.
    25.
    26.
    27.
    28.
    29.
    30.
    31.
    32.
    33.
    3. 发起新纪元leader选举
    基于上述结果redis会判断是否发起故障转移,若需要则通知其他哨兵进行leader选举,收到通知的哨兵会检查当前纪元是否小于发起选举的哨兵纪元,若符合要求且在此期间没有别的哨兵发起选举,则向其投票。

    后续我们的哨兵收到并收集这些响应之后,更新自己所维护的哨兵数组中的leader_epoch,通过遍历这个哨兵数组中的leader_epoch是否和自己所生成的leader_epoch一致,如果统计结果超过半数,则说明自己当选leader,由此开始进行故障转移:



    (1) 选举源码入口

    我们还是以sentinelHandleRedisInstance作为程序入口,可以看到其内部调用sentinelStartFailoverIfNeeded判断是否需要进行故障转移,然后调用sentinelAskMasterStateToOtherSentinels并传入SENTINEL_ASK_FORCED发起leader选举请求:

    复制
    //这个入参包含恰哨兵例和当前主节点的从节点信息
    void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
        //......
        if (ri->flags & SRI_MASTER) {
              //......
            //  判断是否要进行故障切换,若需要则调用sentinelAskMasterStateToOtherSentinels传入SENTINEL_ASK_FORCED进行leader选举
            if (sentinelStartFailoverIfNeeded(ri))
                sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
            // 执行故障切换
            sentinelFailoverStateMachine(ri);
           //......
        }
    }
    1.
    2.
    3.
    4.
    5.
    6.
    7.
    8.
    9.
    10.
    11.
    12.
    13.
    (2) 确认故障转移

    我们步入sentinelStartFailoverIfNeeded即可看到其对于是否进行故障转移的判断,逻辑比较简单:

    明确是否客观认定下线。
    明确是否处于故障转移。
    近期是否有进行故障转移。
    如果伤处条件都排除则:

    failover_state 即故障转移状态设置为等待故障转移,后续的函数状态机会根据这个标识进行故障转移处理。
    flags标识累加处于故障转移中。
    更新master纪元为哨兵纪元+1,用于后续哨兵leader选举后更新纪元使用。
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    快速回复 返回顶部 返回列表