Redis Sentinel 源码分析(3)Sentinel 网络连接和Tilt模式

中间件小哥 发表于 2021/02/04 15:53:04 2021/02/04
【摘要】 在上一篇文章中我们介绍了Sentinel的初始化和主循环,这一篇我们介绍Sentinel的网络连接以及tilt模式。Sentinel的网络连接 在前面的文章曾经提到每个Sentinel实例会维护与所监测的主从实例的两个连接,分别是命令连接(Command  Connection)和发布订阅连接(Pub/Sub Connection)。但是需要注意的是,Sentinel和其他Sentinel之...

在上一篇文章中我们介绍了Sentinel的初始化和主循环,这一篇我们介绍Sentinel的网络连接以及tilt模式。

Sentinel的网络连接

 在前面的文章曾经提到每个Sentinel实例会维护与所监测的主从实例的两个连接,分别是命令连接(Command  Connection)和发布订阅连接(Pub/Sub Connection)。但是需要注意的是,Sentinel和其他Sentinel之间只有一个命令连接。下面将分别介绍命令连接和发布订阅连接的作用。

命令连接

Sentinel维护命令连接是为了与其他主从实例以及Sentinel实例通过发送接收命令的方式进行通信,例如:

  1. Sentinel会默认以每1s间隔发送PING 命令给其他实例以主观判断其他实例是否下线。
  2. Sentinel会通过Sentinel和主实例之间的命令连接每隔10s发送INFO命令给主从实例以得到主实例和从实例的最新信息。
  3. 在主实例下线的情况下,Sentinel会通过Sentinel和从实例的命令连接发送SLAVEOF NO ONE命令给选定的从实例从而使从实例提升为新的主节点。
  4. Sentinel会默认每隔1s发送is-master-down-by-addr命令以询问其他Sentinel节点关于监控的主节点是否下线。

sentinel.c中的sentinelReconnectInstance函数中,命令连接的初始化如下:

/* Commands connection. */  
    if (link->cc == NULL) {  
        link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);  
        if (!link->cc->err && server.tls_replication &&  
                (instanceLinkNegotiateTLS(link->cc) == C_ERR)) {  
            sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS");  
            instanceLinkCloseConnection(link,link->cc);  
        } else if (link->cc->err) {  
            sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",  
                link->cc->errstr);  
            instanceLinkCloseConnection(link,link->cc);  
        } else {  
            link->pending_commands = 0;  
            link->cc_conn_time = mstime();  
            link->cc->data = link;  
            redisAeAttach(server.el,link->cc);  
            redisAsyncSetConnectCallback(link->cc,  
                    sentinelLinkEstablishedCallback);  
            redisAsyncSetDisconnectCallback(link->cc,  
                    sentinelDisconnectCallback);  
            sentinelSendAuthIfNeeded(ri,link->cc);  
            sentinelSetClientName(ri,link->cc,"cmd");  
  
            /* Send a PING ASAP when reconnecting. */  
            sentinelSendPing(ri);  
        }  
}  

发布订阅连接

Sentinel维护和其他主从节点的发布订阅连接作用是为了获知其他监控相同主从实例的Sentinel实例的存在,并且从其他Sentinel实例中更新对所监控的主从实例以及发送的Sentinel实例的认知。例如在主备倒换完成后,其他Sentinel通过读取领头的Sentinel的频道消息来更新新的主节点的相关信息(地址,端口号等)。

Sentinel在默认每隔2秒钟会发送Hello消息包到其对应的主从实例的__sentinel__:hello频道中。Hello消息格式如下:

              __sentinel_:hello <sentinel地址> <sentinel端口号> <sentinel运行id>  <sentinel配置纪元> <主节点名字 > <主节点地址> <主节点端口号> <主节点配置纪元>

Sentinel通过订阅连接收到其他Sentinel发送的的Hello包时,会更新对主从节点以及S发送Sentinel的认知,如果收到自己发送的Hello包,则简单的丢弃不做任何处理。这部分代码逻辑是在sentinel.c中的sentinelProcessHelloMessage函数中定义的,由于篇幅原因在这里不做详细介绍。

sentinel.c中的sentinelReconnectInstance函数中,发布订阅连接初始化如下:

/* Pub / Sub */  
   if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {  
       link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);  
       if (!link->pc->err && server.tls_replication &&  
               (instanceLinkNegotiateTLS(link->pc) == C_ERR)) {  
           sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS");  
       } else if (link->pc->err) {  
           sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",  
               link->pc->errstr);  
           instanceLinkCloseConnection(link,link->pc);  
       } else {  
           int retval;  
  
           link->pc_conn_time = mstime();  
           link->pc->data = link;  
           redisAeAttach(server.el,link->pc);  
           redisAsyncSetConnectCallback(link->pc,  
                   sentinelLinkEstablishedCallback);  
           redisAsyncSetDisconnectCallback(link->pc,  
                   sentinelDisconnectCallback);  
           sentinelSendAuthIfNeeded(ri,link->pc);  
           sentinelSetClientName(ri,link->pc,"pubsub");  
           /* Now we subscribe to the Sentinels "Hello" channel. */  
           retval = redisAsyncCommand(link->pc,  
               sentinelReceiveHelloMessages, ri, "%s %s",  
               sentinelInstanceMapCommand(ri,"SUBSCRIBE"),  
               SENTINEL_HELLO_CHANNEL);  
           if (retval != C_OK) {  
               /* If we can't subscribe, the Pub/Sub connection is useless 
                * and we can simply disconnect it and try again. */  
               instanceLinkCloseConnection(link,link->pc);  
               return;  
           }  
       }  
   } 

is-master-down-by-addr 命令

Sentinel会默认每隔1s通过命令连接发送is-master-down-by-addr命令以询问其他Sentinel节点关于监控的主节点是否下线。另外,在主实例下线的情况下,Sentinel之间也通过is-master-down-by-addr命令来获得投票并选举领头Sentinelis-master-down-by-addr格式如下:

 is-master-down-by-addr:  <主实例地址> <主实例端口号> <当前配置纪元> <运行ID>

如果不是在选举领头Sentinel过程中, <runid>项总为*,相反地,如果在Sentinel向其他Sentinel发送投票请求情况下,<runid>项为自己的运行id。这部分代码如下:

    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,  
                "%s is-master-down-by-addr %s %s %llu %s",  
                sentinelInstanceMapCommand(ri,"SENTINEL"),  
                master->addr->ip, port,  
                sentinel.current_epoch,  
                (master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?  
                sentinel.myid : "*");  
    if (retval == C_OK) ri->link->pending_commands++;  

is-master-down-by-addr的命令回复格式如下:

  • <主节点下线状态>
  • <领头Sentinel运行ID >
  • <领头Sentinel配置纪元>

Sentinel在收到其他Sentinel命令回复后,会记录其他Sentinel回复的主实例在线状态信息,以及在选举领头Sentinel过程中的投票情况,这部分的代码逻辑定义在sentinel.c中的sentinelReceiveIsMasterDownByReply函数:

/* Ignore every error or unexpected reply. 
  * Note that if the command returns an error for any reason we'll 
 * end clearing the SRI_MASTER_DOWN flag for timeout anyway. */    
if (r->type == REDIS_REPLY_ARRAY && r->elements == 3 &&  
     r->element[0]->type == REDIS_REPLY_INTEGER &&  
    r->element[1]->type == REDIS_REPLY_STRING &&  
    r->element[2]->type == REDIS_REPLY_INTEGER)  
{  
    ri->last_master_down_reply_time = mstime();  
    if (r->element[0]->integer == 1) {  
        ri->flags |= SRI_MASTER_DOWN;  
    } else {  
        ri->flags &= ~SRI_MASTER_DOWN;  
    }  

    if (strcmp(r->element[1]->str,"*")) {  
        /* If the runid in the reply is not "*" the Sentinel actually 
         * replied with a vote. */  
        sdsfree(ri->leader);  
  
        if ((long long)ri->leader_epoch != r->element[2]->integer)  {
            serverLog(LL_WARNING,   "%s voted for %s %llu", ri->name, r->element[1]->str,  (unsigned long long) r->element[2]->integer);  
        }
        ri->leader = sdsnew(r->element[1]->str);  
        ri->leader_epoch = r->element[2]->integer;  
    }  
}  

Tilt模式

SentinelTilt模式会在以下两种情况下开启:

  1. Sentinel进程被阻塞超过SENTINEL_TILT_TRIGGER时间(默认为2s),可能因为进程或系统I/O(内存,网络,存储)请求过多。
  2. 系统时钟调整到之前某个时间值。

Tilt模式是一种保护机制,处于该模式下Sentinel除了发送必要的PINGINFO命令外,不会主动做其他操作,例如主备倒换,标志主观、客观下线等。但可以通过INFO 命令及发布订阅连接的HELLO消息包来获取外界信息并对自身结构进行更新,直到SENTINEL_TILT_PERIOD时长(默认为30s)结束为止,我们可以认为Tilt模式是Sentinel的被动模式。

判断Tilt模式的代码逻辑定义如下:

void sentinelCheckTiltCondition(void) {  
    mstime_t now = mstime();  
    mstime_t delta = now - sentinel.previous_time;  
  
    if (delta < 0 || delta > SENTINEL_TILT_TRIGGER) {  
        sentinel.tilt = 1;  
        sentinel.tilt_start_time = mstime();  
        sentinelEvent(LL_WARNING,"+tilt",NULL,"#tilt mode entered");  
    }  
    sentinel.previous_time = mstime();  
}  
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。