Redis源码剖析和注释(二十四)--- Redis Sentinel实现(哨兵操作的深入剖析)

Redis Sentinel实现(下)

本文是Redis Sentinel实现(上)篇文章的下半部分剖析。主要剖析以下内容:

4. 哨兵的使命

sentinel.c文件详细注释:Redis Sentinel详细注释

我们这一部分将会详细介绍标题3.2小节的内容,以下分析,是站在哨兵节点的视角。

我们当时已经简单分析到

  • sentinelHandleRedisInstance()函数用来处理主节点、从节点和哨兵节点的周期性操作。
  • sentinelFailoverSwitchToPromotedSlave()函数用来处理发生主从切换的情况。

因此,先来看第一部分。

4.1 周期性的操作

我们先来看看sentinelHandleRedisInstance()函数中到底实现了哪些操作:

void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
    /* ========== MONITORING HALF ============ */
    /* ========== 一半监控操作 ============ */

    /* Every kind of instance */
    /* 对所有的类型的实例进行操作 */

    // 为Sentinel和ri实例创建一个网络连接,包括cc和pc
    sentinelReconnectInstance(ri);
    // 定期发送PING、PONG、PUBLISH命令到ri实例中
    sentinelSendPeriodicCommands(ri);

    /* ============== ACTING HALF ============= */
    /* ============== 一半故障检测 ============= */

    // 如果Sentinel处于TILT模式,则不进行故障检测
    if (sentinel.tilt) {
        // 如果TILT模式的时间没到,则不执行后面的动作,直接返回
        if (mstime()-sentinel.tilt_start_time < SENTINEL_TILT_PERIOD) return;
        // 如果TILT模式时间已经到了,取消TILT模式的标识
        sentinel.tilt = 0;
        sentinelEvent(LL_WARNING,"-tilt",NULL,"#tilt mode exited");
    }

    /* Every kind of instance */
    // 对于各种实例进行是否下线的检测,是否处于主观下线状态
    sentinelCheckSubjectivelyDown(ri);

    /* Masters and slaves */
    // 目前对主节点和从节点的实例什么都不做
    if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
        /* Nothing so far. */
    }

    /* Only masters */
    // 只对主节点进行操作
    if (ri->flags & SRI_MASTER) {
        // 检查从节点是否客观下线
        sentinelCheckObjectivelyDown(ri);
        // 如果处于客观下线状态,则进行故障转移的状态设置
        if (sentinelStartFailoverIfNeeded(ri))
            // 强制向其他Sentinel节点发送SENTINEL IS-MASTER-DOWN-BY-ADDR给所有的Sentinel获取回复
            // 尝试获得足够的票数,标记主节点为客观下线状态,触发故障转移
            sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
        // 执行故障转移操作
        sentinelFailoverStateMachine(ri);
        // 主节点ri没有处于客观下线的状态,那么也要尝试发送SENTINEL IS-MASTER-DOWN-BY-ADDR给所有的Sentinel获取回复
        // 因为ri主节点如果有回复延迟等等状况,可以通过该命令,更新一些主节点状态
        sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
    }
}

很明显,该函数将周期性的操作分为两个部分,一部分是对一个的实例进行监控的操作,另一部分是对该实例执行故障检测。

接下来就针对上面代码的每一步进行详细剖析,每一个标题对应一步。

4.1.1 建立连接

首先,执行的第一个函数就是sentinelReconnectInstance()函数,因为在载入配置的时候,我们将创建的主节点实例加入到sentinel.masters字典的时候,该主节点的连接是关闭的,所以第一件事就是为主节点和哨兵节点建立网络连接。

查看sentinelReconnectInstance()函数代码:

void sentinelReconnectInstance(sentinelRedisInstance *ri) {
    // 如果ri实例没有连接中断,则直接返回
    if (ri->link->disconnected == 0) return;
    // ri实例地址非法
    if (ri->addr->port == 0) return; /* port == 0 means invalid address. */
    instanceLink *link = ri->link;
    mstime_t now = mstime();

    // 如果还没有最近一次重连的时间距离现在太短,小于1s,则直接返回
    if (now - ri->link->last_reconn_time < SENTINEL_PING_PERIOD) return;
    // 设置最近重连的时间
    ri->link->last_reconn_time = now;

    /* Commands connection. */
    // cc:命令连接
    if (link->cc == NULL) {
        // 绑定ri实例的连接地址并建立连接
        link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
        // 命令连接失败,则事件通知,且断开cc连接
        if (link->cc->err) {
            sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",
                link->cc->errstr);
            instanceLinkCloseConnection(link,link->cc);
        // 命令连接成功
        } else {
            // 重置cc连接的属性
            link->pending_commands = 0;
            link->cc_conn_time = mstime();
            link->cc->data = link;
            // 将服务器的事件循环关联到cc连接的上下文中
            redisAeAttach(server.el,link->cc);
            // 设置确立连接的回调函数
            redisAsyncSetConnectCallback(link->cc,
                    sentinelLinkEstablishedCallback);
            // 设置断开连接的回调处理
            redisAsyncSetDisconnectCallback(link->cc,
                    sentinelDisconnectCallback);
            // 发送AUTH 命令认证
            sentinelSendAuthIfNeeded(ri,link->cc);
            // 发送连接名字
            sentinelSetClientName(ri,link->cc,"cmd");

            /* Send a PING ASAP when reconnecting. */
            // 立即向ri实例发送PING命令
            sentinelSendPing(ri);
        }
    }
    /* Pub / Sub */
    // pc:发布订阅连接
    // 只对主节点和从节点如果没有设置pc连接则建立一个
    if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
        // 绑定指定ri的连接地址并建立连接
        link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
        // pc连接失败,则事件通知,且断开pc连接
        if (link->pc->err) {
            sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",
                link->pc->errstr);
            instanceLinkCloseConnection(link,link->pc);
        // pc连接成功
        } else {
            int retval;

            link->pc_conn_time = mstime();
            link->pc->data = link;
            // 将服务器的事件循环关联到pc连接的上下文中
            redisAeAttach(server.el,link->pc);
            // 设置确立连接的回调函数
            redisAsyncSetConnectCallback(link->pc,
                    sentinelLinkEstablishedCallback);
            // 设置断开连接的回调处理
            redisAsyncSetDisconnectCallback(link->pc,
                    sentinelDisconnectCallback);
            //  发送AUTH 命令认证
            sentinelSendAuthIfNeeded(ri,link->pc);
            // 发送连接名字
            sentinelSetClientName(ri,link->pc,"pubsub");
            // 发送订阅 __sentinel__:hello 频道的命令,设置回调函数处理回复
            // sentinelReceiveHelloMessages是处理Pub/Sub的频道返回信息的回调函数,可以发现订阅同一master的Sentinel节点
            retval = redisAsyncCommand(link->pc,
                sentinelReceiveHelloMessages, ri, "SUBSCRIBE %s",
                    SENTINEL_HELLO_CHANNEL);
            // 订阅频道出错,关闭
            if (retval != C_OK) {
                // 关闭pc连接
                instanceLinkCloseConnection(link,link->pc);
                return;
            }
        }
    }
    // 如果已经建立了新的连接,则清除断开连接的状态。表示已经建立了连接
    if (link->cc && (ri->flags & SRI_SENTINEL || link->pc))
        link->disconnected = 0;
}

建立连接的函数redisAsyncConnectBind()Redis的官方C语言客户端hiredis的异步连接函数,当连接成功时需要调用redisAeAttach()函数来将服务器的事件循环(ae)与连接的上下文相关联起来(因为hiredis提供了多种适配器,包括事件ae,libev,libevent,libuv),在关联的时候,会设置了网络连接的可写可读事件的处理程序。接下来还会设置该连接的确立时和断开时的回调函数redisAsyncSetConnectCallback()redisAsyncSetDisconnectCallback(),为什么这么做,就是因为该连接是异步的。

了解了以上这些,继续分析节点实例和当前哨兵的连接建立。从该函数中可以很明显的看出来:

  • 如论是主节点、从节点还是哨兵节点,都会与当前哨兵建立命令连接(Commands connection)
  • 只有主节点或从节点才会建立发布订阅连接(Pub / Sub connection)

当建立了命令连接(cc)之后立即执行了三个动作

执行函数 执行函数的目的 回复函数 回复函数操作
sentinelSendAuthIfNeeded() 发送 AUTH 命令进行认证 sentinelDiscardReplyCallback() 丢弃回复,只是将已发送但未回复的命令个数减1
sentinelSetClientName() 发送 CLIENT SETNAME命令设置连接的名字 sentinelDiscardReplyCallback() 丢弃回复,只是将已发送但未回复的命令个数减1
sentinelSendPing() 发送 PING 命令来判断连接状态 sentinelPingReplyCallback() 根据回复的内容来更新一些连接交互时间,等。

当建立了发布订阅连接(pc)之后立即执行的动作:(前两个动作与命令连接相同,只列出不相同的第三个)

执行函数 执行函数的目的 回复函数 回复函数操作
redisAsyncCommand() 发送 SUBSCRIBE 命令来判订阅__sentinel__:hello 频道的事件提醒 sentinelReceiveHelloMessages() 哨兵节点处理从实例(主节点或从节点)发送过来的hello信息,来获取其他哨兵节点或主节点的从节点信息。

如果成功建立连接,之后会清除连接断开的标志,以表示连接已建立。

如果不是第一次执行,那么会判断连接是否建立,如果断开,则重新给建立,如果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值