【Redis】哨兵集群-哨兵初始化和主觀下線

在的redis啟動函數(shù)main(server.c文件)中,對哨兵模式進(jìn)行了檢查,如果是哨兵模式,將調(diào)用initSentinelConfig和initSentinel進(jìn)行初始化,initServer函數(shù)中會注冊哨兵的時間事件,最后調(diào)用sentinelIsRunning運(yùn)行哨兵實例,

int main(int argc, char **argv) {
    // 省略...

    // 檢查哨兵模式
    server.sentinel_mode = checkForSentinelMode(argc,argv);
    initServerConfig();
    // 省略...

    if (server.sentinel_mode) {
        initSentinelConfig(); // 初始化哨兵配置
        initSentinel(); // 初始化哨兵
    }
    
    // 省略...
    // 初始化服務(wù)
    initServer();
    // 省略...
    if (!server.sentinel_mode) { // 非哨兵模式
        // 省略...
    } else {
        ACLLoadUsersAtStartup();
        InitServerLast();
        // 運(yùn)行哨兵實例
        sentinelIsRunning();
        if (server.supervised_mode == SUPERVISED_SYSTEMD) {
            redisCommunicateSystemd("STATUS=Ready to accept connections\n");
            redisCommunicateSystemd("READY=1\n");
        }
    }
    // 省略...
  
    aeMain(server.el);
    aeDeleteEventLoop(server.el);
    return 0;
}

哨兵初始化

哨兵模式校驗

checkForSentinelMode

checkForSentinelMode函數(shù)在server.c文件中,用于校驗是否是哨兵模式,可以看到有兩種方式校驗哨兵模式:

  1. 直接執(zhí)行redis-sentinel命令
  2. 執(zhí)行redis-server命令,命令參數(shù)中帶有–sentinel參數(shù)
int checkForSentinelMode(int argc, char **argv) {
    int j;
    // 直接執(zhí)行redis-sentinel
    if (strstr(argv[0],"redis-sentinel") != NULL) return 1;
    for (j = 1; j < argc; j++)
        if (!strcmp(argv[j],"--sentinel")) return 1; // 執(zhí)行的命令參數(shù)中,是否有–sentinel
    return 0;
}

初始化配置項

initSentinelConfig

initSentinelConfig函數(shù)在sentinel.c文件中,用于初始化哨兵配置:

  1. 將哨兵實例的端口號設(shè)置為REDIS_SENTINEL_PORT,默認(rèn)值26379
  2. protected_mode設(shè)置為0,表示允許外部鏈接哨兵實例,而不是只能通過127.0.0.1本地連接 server
#define REDIS_SENTINEL_PORT 26379

void initSentinelConfig(void) {
    server.port = REDIS_SENTINEL_PORT; /* 設(shè)置默認(rèn)端口 */
    server.protected_mode = 0; /* 允許外部鏈接哨兵實例 */
}

initSentinel

在看initSentinel函數(shù)之前,首先看下Redis中哨兵sentinel對象對應(yīng)的結(jié)構(gòu)體sentinelState:

  • current_epoch:當(dāng)前紀(jì)元,投票選舉Leader時使用,紀(jì)元可以理解為投票的輪次
  • masters:監(jiān)控的master節(jié)點(diǎn)哈希表,Key為master節(jié)點(diǎn)名稱, value為master節(jié)點(diǎn)對應(yīng)sentinelRedisInstance實例的指針
struct sentinelState {
    char myid[CONFIG_RUN_ID_SIZE+1]; /* sentinel ID */
    uint64_t current_epoch;         /* 當(dāng)前的紀(jì)元(投票輪次)*/
    dict *masters;      /* 監(jiān)控的master節(jié)點(diǎn)哈希表,Key為master節(jié)點(diǎn)名稱, value為master節(jié)點(diǎn)對應(yīng)的實例對象的指針 */
    int tilt;           /*  TILT模式 */
    int running_scripts;    
    mstime_t tilt_start_time;  
    mstime_t previous_time;         
    list *scripts_queue;           
    char *announce_ip;  
    int announce_port;  
    unsigned long simfailure_flags; 
    int deny_scripts_reconfig; 
    char *sentinel_auth_pass;    
    char *sentinel_auth_user;   
    int resolve_hostnames;       
    int announce_hostnames;      
} sentinel;

sentinelRedisInstance是一個通用的結(jié)構(gòu)體,在sentinel.c文件中定義,它既可以表示主節(jié)點(diǎn),也可以表示從節(jié)點(diǎn)或者其他哨兵實例,從中選選出了一些主要的內(nèi)容:

typedef struct  {
    int flags;      /* 一些狀態(tài)標(biāo)識 */
    char *name;     /* Master name from the point of view of this sentinel. */
    char *runid;    /* 實例的運(yùn)行ID */
    uint64_t config_epoch;  /* 配置的紀(jì)元. */
    mstime_t s_down_since_time; /* 主觀下線時長 */
    mstime_t o_down_since_time; /* 客觀下線時長 */
    dict *sentinels;    /* 監(jiān)控同一主節(jié)點(diǎn)的其他哨兵實例 */
    dict *slaves;       /* slave節(jié)點(diǎn)(從節(jié)點(diǎn)) */
    /* 故障切換 */
    char *leader;       /* 如果是master節(jié)點(diǎn),保存了需要執(zhí)行故障切換的哨兵leader的runid,如果是一個哨兵,保存的是這個哨兵投票選舉的leader*/
    uint64_t leader_epoch; /* leader紀(jì)元 */
    uint64_t failover_epoch; 
    int failover_state; /* 故障切換狀態(tài) */
  
    // 省略...
} sentinelRedisInstance;

initSentinel函數(shù)同樣在sentinel.c文件中,用于初始化哨兵,由于哨兵實例與普通Redis實例不一樣,所以需要替換Redis中的命令,添加哨兵實例命令,哨兵實例使用的命令在sentinelcmds中定義:

  1. 將server.commands和server.orig_commands保存的常規(guī)Redis命令清除
  2. 遍歷sentinelcmds哨兵實例專用命令,將命令添加到server.commands和server.orig_commands中
  3. 初始化sentinel實例中的數(shù)據(jù)項
// 哨兵實例下的命令
struct redisCommand sentinelcmds[] = {
    {"ping",pingCommand,1,"fast @connection",0,NULL,0,0,0,0,0},
    {"sentinel",sentinelCommand,-2,"admin",0,NULL,0,0,0,0,0},
    {"subscribe",subscribeCommand,-2,"pub-sub",0,NULL,0,0,0,0,0},
    {"unsubscribe",unsubscribeCommand,-1,"pub-sub",0,NULL,0,0,0,0,0},
    {"psubscribe",psubscribeCommand,-2,"pub-sub",0,NULL,0,0,0,0,0},
    {"punsubscribe",punsubscribeCommand,-1,"pub-sub",0,NULL,0,0,0,0,0},
    {"publish",sentinelPublishCommand,3,"pub-sub fast",0,NULL,0,0,0,0,0},
    {"info",sentinelInfoCommand,-1,"random @dangerous",0,NULL,0,0,0,0,0},
    {"role",sentinelRoleCommand,1,"fast read-only @dangerous",0,NULL,0,0,0,0,0},
    {"client",clientCommand,-2,"admin random @connection",0,NULL,0,0,0,0,0},
    {"shutdown",shutdownCommand,-1,"admin",0,NULL,0,0,0,0,0},
    {"auth",authCommand,-2,"no-auth fast @connection",0,NULL,0,0,0,0,0},
    {"hello",helloCommand,-1,"no-auth fast @connection",0,NULL,0,0,0,0,0},
    {"acl",aclCommand,-2,"admin",0,NULL,0,0,0,0,0,0},
    {"command",commandCommand,-1, "random @connection", 0,NULL,0,0,0,0,0,0}
};

/* 初始化哨兵 */
void initSentinel(void) {
    unsigned int j;

    /* 將常規(guī)的Redis命令移除,增加哨兵實例專用的命令 */
    dictEmpty(server.commands,NULL);
    dictEmpty(server.orig_commands,NULL);
    ACLClearCommandID();
    for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) {
        int retval;
        struct redisCommand *cmd = sentinelcmds+j;
        cmd->id = ACLGetCommandID(cmd->name);
        // 添加到server.commands
        retval = dictAdd(server.commands, sdsnew(cmd->name), cmd);
        serverAssert(retval == DICT_OK);
        // 添加到server.orig_commands
        retval = dictAdd(server.orig_commands, sdsnew(cmd->name), cmd);
        serverAssert(retval == DICT_OK);
        if (populateCommandTableParseFlags(cmd,cmd->sflags) == C_ERR)
            serverPanic("Unsupported command flag");
    }

    /* 初始化其他數(shù)據(jù)項 */
    // current_epoch初始化為0
    sentinel.current_epoch = 0;
    // 監(jiān)控的master節(jié)點(diǎn)實例對象
    sentinel.masters = dictCreate(&instancesDictType,NULL);
    sentinel.tilt = 0;
    sentinel.tilt_start_time = 0;
    sentinel.previous_time = mstime();
    sentinel.running_scripts = 0;
    sentinel.scripts_queue = listCreate();
    sentinel.announce_ip = NULL;
    sentinel.announce_port = 0;
    sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
    sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG;
    sentinel.sentinel_auth_pass = NULL;
    sentinel.sentinel_auth_user = NULL;
    sentinel.resolve_hostnames = SENTINEL_DEFAULT_RESOLVE_HOSTNAMES;
    sentinel.announce_hostnames = SENTINEL_DEFAULT_ANNOUNCE_HOSTNAMES;
    memset(sentinel.myid,0,sizeof(sentinel.myid));
    server.sentinel_config = NULL;
}

啟動哨兵實例

sentinelIsRunning

sentinelIsRunning函數(shù)在sentinel.c文件中,用于啟動哨兵實例:

  1. 校驗是否設(shè)置了哨兵實例的ID,如果未設(shè)置,將隨機(jī)生成一個ID
  2. 調(diào)用sentinelGenerateInitialMonitorEvents向監(jiān)控的主節(jié)點(diǎn)發(fā)送+monitor事件
void sentinelIsRunning(void) {
    int j;

    /* 校驗myid是否為0 */
    for (j = 0; j < CONFIG_RUN_ID_SIZE; j++)
        if (sentinel.myid[j] != 0) break;

    if (j == CONFIG_RUN_ID_SIZE) {
        /* 隨機(jī)生成ID */
        getRandomHexChars(sentinel.myid,CONFIG_RUN_ID_SIZE);
        sentinelFlushConfig();
    }

    serverLog(LL_WARNING,"Sentinel ID is %s", sentinel.myid);

    /* 向監(jiān)控的主節(jié)點(diǎn)發(fā)送+monitor事件 */
    sentinelGenerateInitialMonitorEvents();
}

/* 向監(jiān)控的主節(jié)點(diǎn)發(fā)布事件 */
void sentinelGenerateInitialMonitorEvents(void) {
    dictIterator *di;
    dictEntry *de;
    // 獲取監(jiān)控的主節(jié)點(diǎn)
    di = dictGetIterator(sentinel.masters);
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *ri = dictGetVal(de);
        // 向主節(jié)點(diǎn)發(fā)送監(jiān)控事件
        sentinelEvent(LL_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
    }
    dictReleaseIterator(di);
}

哨兵時間事件

在initServer函數(shù)中,調(diào)用aeCreateTimeEvent注冊了時間事件,周期性的執(zhí)行serverCron函數(shù),serverCron函數(shù)中通過server.sentinel_mode判斷是否是哨兵模式,如果是哨兵模式,調(diào)用sentinelTimer執(zhí)行哨兵事件:

void initServer(void) {
    
    // 省略...
    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        serverPanic("Can't create event loop timers.");
        exit(1);
    }
    // 省略...
}

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    // 省略...
    
    // 如果是哨兵模式,調(diào)用sentinelTimer執(zhí)行哨兵事件
    if (server.sentinel_mode) sentinelTimer();
    
    // 省略...
}

sentinelTimer

sentinelTimer在sentinel.c文件中,sentinelTimer函數(shù)會周期性的執(zhí)行:

void sentinelTimer(void) {
    sentinelCheckTiltCondition();
    // 處理RedisInstances,傳入的參數(shù)是當(dāng)前哨兵實例維護(hù)的主節(jié)點(diǎn)的哈希表,里面記錄當(dāng)前節(jié)點(diǎn)監(jiān)聽的主節(jié)點(diǎn)
    sentinelHandleDictOfRedisInstances(sentinel.masters);
    sentinelRunPendingScripts();
    sentinelCollectTerminatedScripts();
    sentinelKillTimedoutScripts();
    // 調(diào)整sentinelTimer的執(zhí)行頻率
    server.hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;
}

sentinelHandleDictOfRedisInstances

sentinelHandleDictOfRedisInstances函數(shù)中會對傳入的當(dāng)前哨兵實例監(jiān)聽的主節(jié)點(diǎn)哈希表進(jìn)行遍歷:

  1. 獲取哈希表中的每一個節(jié)點(diǎn),節(jié)點(diǎn)類型是sentinelRedisInstance
  2. 調(diào)用sentinelHandleRedisInstance檢測哨兵監(jiān)聽節(jié)點(diǎn)的狀態(tài)
  3. 如果sentinelHandleRedisInstance是主節(jié)點(diǎn),由于主節(jié)點(diǎn)里面保存了監(jiān)聽該主節(jié)點(diǎn)的其他哨兵實例以及從節(jié)點(diǎn),所以遞歸調(diào)用sentinelHandleDictOfRedisInstances對其他的節(jié)點(diǎn)進(jìn)行檢測
/* sentinelHandleDictOfRedisInstances */
void sentinelHandleDictOfRedisInstances(dict *instances) {
    dictIterator *di;
    dictEntry *de;
    sentinelRedisInstance *switch_to_promoted = NULL;
    di = dictGetIterator(instances);
    // 遍歷所有的sentinelRedisInstance實例
    while((de = dictNext(di)) != NULL) {
        // 獲取每一個sentinelRedisInstance
        sentinelRedisInstance *ri = dictGetVal(de);
        // 調(diào)用sentinelHandleRedisInstance檢測哨兵監(jiān)聽節(jié)點(diǎn)的狀態(tài)
        sentinelHandleRedisInstance(ri);
        // 如果是sentinelRedisInstance是主節(jié)點(diǎn),主節(jié)點(diǎn)里面保存了監(jiān)聽該主節(jié)點(diǎn)的其他哨兵實例以及從節(jié)點(diǎn)
        if (ri->flags & SRI_MASTER) {
            // 遞歸調(diào)用,處理從節(jié)點(diǎn)
            sentinelHandleDictOfRedisInstances(ri->slaves);
            // 遞歸調(diào)用,處理其他哨兵實例
            sentinelHandleDictOfRedisInstances(ri->sentinels);
            if (ri->failover_state == SENTINEL_FAILOVER_STATE_UPDATE_CONFIG) {
                switch_to_promoted = ri;
            }
        }
    }
    if (switch_to_promoted)
        sentinelFailoverSwitchToPromotedSlave(switch_to_promoted);
    dictReleaseIterator(di);
}

檢測哨兵監(jiān)聽的節(jié)點(diǎn)狀態(tài)

sentinelHandleRedisInstance

sentinelHandleRedisInstance函數(shù)對傳入的sentinelRedisInstance實例,進(jìn)行狀態(tài)檢測,主要處理邏輯如下:

  1. 調(diào)用sentinelReconnectInstance對實例的連接狀態(tài)進(jìn)行判斷,如果**連接中斷嘗試重新與實例建立連接 **
  2. 調(diào)用sentinelSendPeriodicCommands向?qū)嵗l(fā)送PING INFO等命令
  3. sentinelCheckSubjectivelyDown判斷實例是否主觀下線
  4. 如果實例是master節(jié)點(diǎn),調(diào)用sentinelCheckObjectivelyDown判斷是否客觀下線、是否需要執(zhí)行故障切換

/* Perform scheduled operations for the specified Redis instance. */
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {

    // 如果監(jiān)聽的節(jié)點(diǎn)連接中斷,嘗試重新建立連接 
    sentinelReconnectInstance(ri);
    // 發(fā)送PING INFO等命令
    sentinelSendPeriodicCommands(ri);

    // 檢查是否是TILT模式
    if (sentinel.tilt) {
        if (mstime()-sentinel.tilt_start_time < SENTINEL_TILT_PERIOD) return;
        sentinel.tilt = 0;
        sentinelEvent(LL_WARNING,"-tilt",NULL,"#tilt mode exited");
    }

    // 判斷主觀下線
    sentinelCheckSubjectivelyDown(ri);

    /* Masters and slaves */
    if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
    }

    // 如果是master節(jié)點(diǎn)
    if (ri->flags & SRI_MASTER) {
        // 判斷客觀下線
        sentinelCheckObjectivelyDown(ri);
        // 是否需要啟動故障切換
        if (sentinelStartFailoverIfNeeded(ri))
            sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
        // 執(zhí)行故障切換
        sentinelFailoverStateMachine(ri);
        // 獲取其他哨兵實例對master節(jié)點(diǎn)狀態(tài)的判斷
        sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
    }
}

重新連接

sentinelReconnectInstance

sentinelReconnectInstance函數(shù)用于檢測實例的連接狀態(tài),如果中斷進(jìn)行重連,主要處理邏輯如下:

  1. 檢測連接是否中斷,如果未中斷直接返回

  2. 檢查端口是否為0,0被認(rèn)為是不合法的端口

  3. 從sentinelRedisInstance實例中獲取instanceLink,instanceLink的定義在sentinel.c文件中,里面記錄了哨兵和主節(jié)點(diǎn)的兩個連接,分別為用于發(fā)送命令的連接cc和用于發(fā)送Pub/Sub消息的連接pc

    typedef struct instanceLink {
        int refcount;         
        int disconnected;      
        int pending_commands;  
        redisAsyncContext *cc; /* 用于發(fā)送命令的連接 */
        redisAsyncContext *pc; /* 用于發(fā)送Pub/Sub消息的連接 */
        mstime_t cc_conn_time; /* cc 連接時間 */
        mstime_t pc_conn_time; /* pc 連接時間 */
        mstime_t pc_last_activity; 
        mstime_t last_avail_time; /* 上一次收到實例回復(fù)PING命令(需要被認(rèn)定為合法)的時間 */  
        mstime_t act_ping_time; /* 當(dāng)收到PONG消息的時候會設(shè)置為0,在下次發(fā)送PING命令時設(shè)置為當(dāng)前時間 */  
        mstime_t last_ping_time;  /* 上次發(fā)送PING命令時間,在出現(xiàn)故障時可以通過判斷發(fā)送時間避免多次發(fā)送PING命令 */
        mstime_t last_pong_time;  /* 上次收到PONG消息的時間 */
        mstime_t last_reconn_time;  /* 上次執(zhí)行重連的時間 */
    } instanceLink;
    
  4. 校驗距離上次重連時間是否小于PING的檢測周期SENTINEL_PING_PERIOD,如果小于說明距離上次重連時間過近,直接返回即可

    SENTINEL_PING_PERIOD在server.c中定義,默認(rèn)1000毫秒

    #define SENTINEL_PING_PERIOD 1000
    
  5. 對用于發(fā)送命令的連接判斷,如果連接為NULL,調(diào)用redisAsyncConnectBind函數(shù)進(jìn)行重連

  6. 對用于處理發(fā)送 Pub/Sub 消息的連接進(jìn)行判斷,如果連接為NULL,調(diào)用redisAsyncConnectBind函數(shù)進(jìn)行重連

void sentinelReconnectInstance(sentinelRedisInstance *ri) {
    // 檢查連接是否中斷
    if (ri->link->disconnected == 0) return;
    if (ri->addr->port == 0) return; /* 檢查端口是否為0,0被認(rèn)為是不合法的端口 */
    // 獲取instanceLink
    instanceLink *link = ri->link;
    mstime_t now = mstime();
    // 校驗距離上次重連時間是否小于哨兵PING的周期設(shè)置
    if (now - ri->link->last_reconn_time < SENTINEL_PING_PERIOD) return;
    ri->link->last_reconn_time = now;

    /* 處理用于發(fā)送命令的連接 */
    if (link->cc == NULL) {
        // 進(jìn)行連接
        link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
        if (link->cc && !link->cc->err) anetCloexec(link->cc->c.fd);
        // 省略...
    }
    /* 處理用于發(fā)送 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 && !link->pc->err) anetCloexec(link->pc->c.fd);
        // 省略...
    }
    if (link->cc && (ri->flags & SRI_SENTINEL || link->pc))
        link->disconnected = 0;
}

發(fā)送命令

sentinelSendPeriodicCommands

sentinelSendPeriodicCommands用于向?qū)嵗l(fā)送命令:

void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
    mstime_t now = mstime();
    mstime_t info_period, ping_period;
    int retval;
    
    // 省略...
    
    /* 向主節(jié)點(diǎn)和從節(jié)點(diǎn)發(fā)送INFO命令 */
    if ((ri->flags & SRI_SENTINEL) == 0 &&
        (ri->info_refresh == 0 ||
        (now - ri->info_refresh) > info_period))
    {
        // 發(fā)送INFO命令
        retval = redisAsyncCommand(ri->link->cc,
            sentinelInfoReplyCallback, ri, "%s",
            sentinelInstanceMapCommand(ri,"INFO"));
        if (retval == C_OK) ri->link->pending_commands++;
    }
    
    if ((now - ri->link->last_pong_time) > ping_period &&
               (now - ri->link->last_ping_time) > ping_period/2) {
        // 向?qū)嵗l(fā)送PING命令
        sentinelSendPing(ri);
    }

    /* PUBLISH hello messages to all the three kinds of instances. */
    if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
        // 發(fā)送PUBLISH命令
        sentinelSendHello(ri);
    }
}

主觀下線

sentinelCheckSubjectivelyDown

sentinelCheckSubjectivelyDown函數(shù)用于判斷是否主觀下線。

標(biāo)記主觀下線的兩個條件

  • 距離上次發(fā)送PING命令的時長超過了down_after_period的值,down_after_period的值在sentinel.conf 配置文件中配置,對應(yīng)的配置項為down-after-milliseconds ,默認(rèn)值30s
  • 哨兵認(rèn)為實例是主節(jié)點(diǎn)(ri->flags & SRI_MASTE),但是實例向哨兵返回的角色是從節(jié)點(diǎn)(ri->role_reported == SRI_SLAVE) 并且當(dāng)前時間-實例報告消息的時間role_reported_time大于down_after_period加上SENTINEL_INFO_PERIOD乘以2的時間 ,SENTINEL_INFO_PERIOD 是發(fā)送INFO命令的時間間隔,也就是說實例上次成功向哨兵報告角色的時間,已經(jīng)超過了限定時間(down_after_period加上SENTINEL_INFO_PERIOD2)*

滿足以上兩個條件之一哨兵將會把sentinelRedisInstance判斷為主觀下線,flag標(biāo)記會添加SRI_S_DOWN狀態(tài)。

void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
    mstime_t elapsed = 0;
    // 如果act_ping_time不為0
    if (ri->link->act_ping_time)
        elapsed = mstime() - ri->link->act_ping_time; // 計算距離上次發(fā)送PING命令的間隔時間
    else if (ri->link->disconnected) // 如果連接斷開
        elapsed = mstime() - ri->link->last_avail_time; // 計算距離最近一次收到PING命令回復(fù)的間隔時間

    if (ri->link->cc &&
        (mstime() - ri->link->cc_conn_time) >
        SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
        ri->link->act_ping_time != 0 && 
        (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);
    }

    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);
    }

    /* 
     * 標(biāo)記主觀下線的兩個條件(或的關(guān)系)
     * 1) 距離上次發(fā)送PING命令的時長超過了down_after_period
     * 2) 哨兵認(rèn)為實例是主節(jié)點(diǎn)(ri->flags & SRI_MASTE),但是實例向哨兵返回的角色是從節(jié)點(diǎn)(ri->role_reported == SRI_SLAVE) 并且當(dāng)前時間-實例返回消息的時間大于down_after_period加上SENTINEL_INFO_PERIOD*2的時間 */
    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)))
    {
        /* 主觀下線 */
        if ((ri->flags & SRI_S_DOWN) == 0) {
            // 發(fā)送+sdown事件
            sentinelEvent(LL_WARNING,"+sdown",ri,"%@");
            ri->s_down_since_time = mstime();
            ri->flags |= SRI_S_DOWN; // 更改狀態(tài)
        }
    } else {
        /* Is subjectively up */
        if (ri->flags & SRI_S_DOWN) {
            sentinelEvent(LL_WARNING,"-sdown",ri,"%@");
            ri->flags &= ~(SRI_S_DOWN|SRI_SCRIPT_KILL_SENT);
        }
    }
}

客觀下線

如果是主節(jié)點(diǎn),將會調(diào)用sentinelCheckObjectivelyDown函數(shù)判斷客觀下線,之后調(diào)用sentinelStartFailoverIfNeeded判斷是否需要執(zhí)行故障切換。

總結(jié)

2612945-20220503194709281-799364610.png

參考

極客時間 - Redis源碼剖析與實戰(zhàn)(蔣德鈞)

Redis版本:redis-6.2.5

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 寫在前 持久化解決了單機(jī)redis的數(shù)據(jù)保存問題,但是redis還是存在以下兩個問題: 假如某天這臺redis服務(wù)...
    _code_x閱讀 2,105評論 1 6
  • sentinel是redis的高可用性解決方案:由一個或多個sentinel實例組成sentinel系統(tǒng)監(jiān)視多個m...
    wh4763閱讀 338評論 0 0
  • Redis集群 主從復(fù)制 相關(guān)文檔 簡介 主從復(fù)制,是指將一臺Redis服務(wù)器的數(shù)據(jù),復(fù)制到其他的Redis服務(wù)器...
    Iktsuarpok_9閱讀 235評論 0 0
  • 1.持久化 方式 RDB AOF RDB+AOF 無 RDB vs AOF RDB的優(yōu)缺點(diǎn) 優(yōu)點(diǎn)文件緊湊非常適合災(zāi)...
    laowangv2閱讀 242評論 0 0
  • 一、Redis主從復(fù)制 主從復(fù)制:主節(jié)點(diǎn)負(fù)責(zé)寫數(shù)據(jù),從節(jié)點(diǎn)負(fù)責(zé)讀數(shù)據(jù),主節(jié)點(diǎn)定期把數(shù)據(jù)同步到從節(jié)點(diǎn)保證數(shù)據(jù)的一致性...
    愛情小傻蛋閱讀 1,089評論 0 0

友情鏈接更多精彩內(nèi)容