【Redis源码】集群之哨兵sentinel故障转移 (十二)

20 篇文章 3 订阅

前言:

各位看官大家好,这个主题内容比较长然后接着上一章就拆成了两个部分。那么我们接着上一章内容开始说。上一章中我们说到哨兵定时器sentinelTimer它们作用。sentinelTimer方法中执行哨兵模式中的任务。包括执行定期操作比如PING、分析主服务和从服务的INFO命令、故障转移等等。那么这一章我们就先从sentinelTimer开始说起。

(一) 基本结构

1.1 sentinelTimer 定时程序

在这里插入图片描述
图中为sentinelTimer调用链路,须线部分为调用aeCreateTimeEvent注册serverCron事件。
sentinel.c 中sentinelTimer方法:

void sentinelTimer(void) {
    //检测是否需要开启sentinel TILT模式
    sentinelCheckTiltCondition();
    //对哈希表中的每个服务器实例执行调度任务
    sentinelHandleDictOfRedisInstances(sentinel.masters);
    //执行脚本命令,
    sentinelRunPendingScripts();
    //清理已经执行完脚本的进程,
    sentinelCollectTerminatedScripts();
    //kill执行时间超时的脚本
    sentinelKillTimedoutScripts();
    /*
    * 为了防止多个哨兵同时选举时故意错开定时程序执行的时间。
    */
    server.hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;
}

1.2 哨兵结构介绍

基本数据结构

struct sentinelState {
    char myid[CONFIG_RUN_ID_SIZE+1]; /* 哨兵ID */
    uint64_t current_epoch;         /* Current epoch. */
    dict *masters;                  /* 存储哨兵监听服务器的信息 对应一个
                                    sentinelRedisInstance 结构体指针 */
    int tilt;                       /* 判断 TILT 模式 */
    int running_scripts;            /* 当前正在执行的脚本数。 */
    mstime_t tilt_start_time;       /*  TITL 开始时间. */
    mstime_t previous_time;         /* 上次处理程序运行时间. */
    list *scripts_queue;           /* 要执行的用户脚本队列. */
    char *announce_ip;             /* IP 地址(gossip协议握手地址) */
    int announce_port;              /* 端口 (gossip协议端口) */
    unsigned long simfailure_flags; /* 故障模拟状态. */
} sentinel;

每一个哨兵都有一个sentinel结构,里面维护着多个主机连接。每个主机连接信息都维护着一个sentinelRedisInstance,通过这个结构维护着所有主机连接的关系。

sentinelRedisInstance结构信息:
typedef struct sentinelRedisInstance {
    int flags;      /* 记录哨兵类型以及实力当前状态 */
    char *name;     /* 哨兵名称格式为ip:port ,例如"127.0.0.1:26379" */
    char *runid;    /* 哨兵运行ID.*/
    uint64_t config_epoch;  /* 配置纪元,用于故障转移. */
    sentinelAddr *addr; /* 实例地址. */
     
     //...省略
     
    mstime_t s_down_since_time; /* 主观下线标记时间. */
    mstime_t o_down_since_time; /* 客观下线标记时间. */
    mstime_t down_after_period; /* 实例无响应多少毫秒之后才会被判断为主观下线(subjectively down ),SENTINEL down-after-milliseconds 选项设定的值 */
  
    //。。。省略
    /* Master specific. */
    dict *sentinels;    /* 其他sentinels. */
    dict *slaves;       /* 这个master的slave */
    unsigned int quorum;/* 判断这个实例客观下线(objectively down )所需的支持投票数量,SENTINEL monitor <master-name> <IP> <port> <quorum> 选项中的quorum 参数 */
    int parallel_syncs; /* 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量,SENTINEL parallel-syncs <master-name> <number> 选项的值. */
    char *auth_pass;    /* Password to use for AUTH against master & slaves. */

    //。。。省略
    mstime_t failover_start_time;   /* 上次故障转移尝试开始时间. */
    mstime_t failover_timeout;      /* 刷新故障迁移状态的最大时间限制. SENTINEL failover-timeout <master-name> <ms> 选项的值*/
    
    //。。。省略
} sentinelRedisInstance;

在这里插入图片描述

1.3 sentinel建立网络连接

创建与被监听的master网络连接后,sentinel会成功master的客户端,它会向master发送命令。
并从master的响应中获取master的信息。对于每个被监听者的master,sentinel会向创建两个异步的网络连接。
该连接通过sentinelReconnectInstance函数创建,一个链接为commands链接。另外一个链接为Pub / Sub 连接。订阅发布会创建一个__sentinel__:hello的通道。

1.4 sentinel命令集

# 重置名字匹配正则表达式的所有master状态信息,清除之前存储的状态信息和slaves信息。PS:节点只要加入过sentinel,信息就会保存而不会自动清除  
sentinel reset <pattern>

# 用于改变关于master的配置,例如 sentinel set mymaster down-after-milliseconds 1000 ,此命令修改了当节点第一次失去连接到判定其下线所经过的时间
sentinel set <name> <option> <value>

# 告诉sentinel去监听新的master 
sentinel monitor <name> <ip> <port> <quorum>  

# 命令sentinel放弃对某个master的监听
sentinel remove <name>  

# 这个参数设置集群从判断节点挂掉,到执行故障转移操作(即重新选举master节点)的时间
sentinel failover-timeout mymaster 10000

# 获取哨兵监视某个sentinel的信息
sentinel sentinels <master-name>

# 获取sentinel监视的某个master的slaves信息
sentinel slaves <master-name>

# 获取sentinel 监视的某个 master信息
sentinel master <name>

# 获取sentinel监视所有的master信息
sentinel masters

# 询问该sentinel,该 ip,port的master是否为down状态,
# 如果该sentinel为tilt模式,会不理会这个询问,不去判断
# 该master是否为主观下线状态,直接回复正常状态。
sentinel is-master-down-by-addr <ip> <port> <current-epoch> <runid>

# 根据master名字获取到master的ip和port
sentinel get-master-addr-by-name <master-name>

# 将sentinel 状态信息写入到配置文件当中
setinel flushconfig

# 检查可投票同意master on failure的sentinel+1的个数以及相关状态
# (可用的投票个数是否大于master 的quorum,需要quorum个同意master on failure)
setinel ckquorum <name>

(二)发现故障

2.1如何确定故障

提及到确认故障,哨兵中确认故障有两种形式分为对应两种状态SRI_S_DOWN(主观下线)和SRI_O_DOWN(客观下线)。

1) 主观下线

主观下线会涉及到一个方法sentinelCheckSubjectivelyDown,图中会主观下线链路
在这里插入图片描述

void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
    mstime_t elapsed = 0;

    if (ri->link->act_ping_time)
        elapsed = mstime() - ri->link->act_ping_time;
    else if (ri->link->disconnected)
        elapsed = mstime() - ri->link->last_avail_time;

    /* 检测command 连接是否被关闭 */
    if (ri->link->cc &&
        (mstime() - ri->link->cc_conn_time) >
        SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
        ri->link->act_ping_time != 0 && /* Ther is a pending ping... */
        /* The pending ping is delayed, and we did not received
         * error replies as well. */
        (mstime() - ri->link->act_ping_time) > (ri->down_after_period/2) &&
        (mstime() - ri->link->last_pong_time) > (ri->down_after_period/2))
    {
        instanceLinkCloseConnection(ri->link,ri->link->cc);
    }

    /* 检测pubsub连接是否需要被关闭 
     */
    if (ri->link->pc &&
        (mstime() - ri->link->pc_conn_time) >
         SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
        (mstime() - ri->link->pc_last_activity) > (SENTINEL_PUBLISH_PERIOD*3))
    {
        instanceLinkCloseConnection(ri->link,ri->link->pc);
    }

    /* 更新SRI_S_DOWN状态
     */
    if (elapsed > ri->down_after_period ||
        (ri->flags & SRI_MASTER &&
         ri->role_reported == SRI_SLAVE &&
         mstime() - ri->role_reported_time >
          (ri->down_after_period+SENTINEL_INFO_PERIOD*2)))
    {
        /* Is subjectively down */
        if ((ri->flags & SRI_S_DOWN) == 0) {
            sentinelEvent(LL_WARNING,"+sdown",ri,"%@");
            ri->s_down_since_time = mstime();
            ri->flags |= SRI_S_DOWN;
        }
    } else {
        /* Is subjectively up */
        if (ri->flags & SRI_S_DOWN) {
            sentinelEvent(LL_WARNING,"-sdown",ri,"%@");
            ri->flags &= ~(SRI_S_DOWN|SRI_SCRIPT_KILL_SENT);
        }
    }
}
  1. 检测command 连接是否被关闭;
    2.检测pubsub连接是否需要被关闭 ;
    3.更新SRI_S_DOWN状态状态,更新状态有如下两个规则:
    3.1 超过ri->down_after_period,代表超过响应时间,及ping无响应请求。该时间默认走的时SENTINEL_DEFAULT_DOWN_AFTER宏为30s。
    3.2 SLAVE上报连续时间间隔要大于ri->down_after_period+SENTINEL_INFO_PERIOD2,及(30s + 10s2),如果超过这个时间代表slave长时间连续不到master,所以视为主观下线。

2) 客观下线

说到客观下线是,我们要思考一个问题。当一台master服务已经掉线,并且已经维护自己的状态为SRI_S_DOWN。由于在哨兵集群中,ri->down_after_period值可能不一样。判断master下线的时间间隔可能不一样。所以必须去询问sentinel节点这台master服务是否下线。

void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
    //。。。省略
    /* Only masters */
    if (ri->flags & SRI_MASTER) {
        sentinelCheckObjectivelyDown(ri);
        if (sentinelStartFailoverIfNeeded(ri))
            sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
        sentinelFailoverStateMachine(ri);
        sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);  
    }
}

在上面代码中可以看到客观下线只能master中使用。然后看一下sentinelAskMasterStateToOtherSentinels方法,该方法检测master主观下线后去询问其他sentinel。

void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int flags) {
    dictIterator *di;
    dictEntry *de;

    di = dictGetIterator(master->sentinels);
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *ri = dictGetVal(de);
        mstime_t elapsed = mstime() - ri->last_master_down_reply_time;
        char port[32];
        int retval;

        /* If the master state from other sentinel is too old, we clear it. */
        if (elapsed > SENTINEL_ASK_PERIOD*5) {
            ri->flags &= ~SRI_MASTER_DOWN;
            sdsfree(ri->leader);
            ri->leader = NULL;
        }

        /* 满足下列情况才可以询问其他哨兵:
         *
         * 1) 主观下线是否在进行
         * 2) Sentinel是否连接
         * 3) 我们没有在哨兵询问期内收到信息,1秒内. */
        if ((master->flags & SRI_S_DOWN) == 0) continue;
        if (ri->link->disconnected) continue;
        if (!(flags & SENTINEL_ASK_FORCED) &&
            mstime() - ri->last_master_down_reply_time < SENTINEL_ASK_PERIOD)
            continue;

        /* Ask */
        ll2string(port,sizeof(port),master->addr->port);
        retval = redisAsyncCommand(ri->link->cc,
                    sentinelReceiveIsMasterDownReply, ri,
                    "SENTINEL is-master-down-by-addr %s %s %llu %s",
                    master->addr->ip, port,
                    sentinel.current_epoch,
                    (master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
                    sentinel.myid : "*");
        if (retval == C_OK) ri->link->pending_commands++;
    }
    dictReleaseIterator(di);
}

通过遍历维护的master->sentinels结构向其他sentinel节点发送命令:SENTINEL is-master-down-by-addr
命令格式如下:
SENTINEL is-master-down-by-addr <current_epoch> <leader_id>

命令询问其他sentinel是否同意主服务器已下线。

接受SENTINEL is-master-down-by-addr命令返回状态:
<down_state> <leader_runid> <leader_epoch>

down_state:为1代表主服务器已下线,0表示主服务器未下线。
leader_runid:领头sentinal id。
leader_epoch:领头sentinel当前投票纪元。

(三)故障转移

3.1 故障状态

当某个主节点进行故障转移时,该主节点的的故障转移状态,master->failover_state,依次会经历6个状态:
状态宏:

SENTINEL_FAILOVER_STATE_NONE 0                 /*没有故障转移在进行*/
//以下为经历的6个状态
SENTINEL_FAILOVER_STATE_WAIT_START 1           /* sentinel接手故障转移*/
SENTINEL_FAILOVER_STATE_SELECT_SLAVE 2         /* 选择slave成为master*/
SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE 3   /* 发送slaveof no one给新master */
SENTINEL_FAILOVER_STATE_WAIT_PROMOTION 4       /* 等待新master升级完成,超时终止故障转移*/
SENTINEL_FAILOVER_STATE_RECONF_SLAVES 5        /* 新master升级完成后,让slaves复制新master */
SENTINEL_FAILOVER_STATE_UPDATE_CONFIG 6        /* 监视新master */

3.2 状态机

void sentinelFailoverStateMachine(sentinelRedisInstance *ri) {
    serverAssert(ri->flags & SRI_MASTER);

    if (!(ri->flags & SRI_FAILOVER_IN_PROGRESS)) return;

    switch(ri->failover_state) {
        case SENTINEL_FAILOVER_STATE_WAIT_START:
            sentinelFailoverWaitStart(ri);         //sentinel接手故障转移
            break;
        case SENTINEL_FAILOVER_STATE_SELECT_SLAVE:
            sentinelFailoverSelectSlave(ri);      //选择slave成为master
            break;
        case SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE:
            sentinelFailoverSendSlaveOfNoOne(ri); //发送slaveof no one给新master
            break;
        case SENTINEL_FAILOVER_STATE_WAIT_PROMOTION:
            sentinelFailoverWaitPromotion(ri);    //等待新master升级完成,超时终止故障转移
            break;
        case SENTINEL_FAILOVER_STATE_RECONF_SLAVES:
            sentinelFailoverReconfNextSlave(ri); //新master升级完成后,让slaves复制新master
            break;
    }
}

状态机变化过程:
在这里插入图片描述

总结

1)一个哨兵结构中可以维护多个主机,包括master,slave,sentinel。
2)确定故障分为:主观下线和客观下线两种。
3)主观下线:为一段时间内ping返回无效,探测所有节点都是一致的,则为主观下线。主观下线的时间是可以配置的,以master配置维度为准。
4)客观下线:客观下线只针对于master节点,且需要master为主观下线,并通过其他sentinel节点发送SENTINEL is-master-down-by-addr询问其他节点master下线问题,达成共识的一个状态。
5) 哨兵会创建两个链接一个commands链接,一个订阅发布链接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值