在的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文件中,用于校驗是否是哨兵模式,可以看到有兩種方式校驗哨兵模式:
- 直接執(zhí)行
redis-sentinel命令 - 執(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文件中,用于初始化哨兵配置:
- 將哨兵實例的端口號設(shè)置為
REDIS_SENTINEL_PORT,默認(rèn)值26379 - 將
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中定義:
- 將server.commands和server.orig_commands保存的常規(guī)Redis命令清除
- 遍歷sentinelcmds哨兵實例專用命令,將命令添加到server.commands和server.orig_commands中
- 初始化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文件中,用于啟動哨兵實例:
- 校驗是否設(shè)置了哨兵實例的ID,如果未設(shè)置,將隨機(jī)生成一個ID
- 調(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)行遍歷:
- 獲取哈希表中的每一個節(jié)點(diǎn),節(jié)點(diǎn)類型是sentinelRedisInstance
- 調(diào)用sentinelHandleRedisInstance檢測哨兵監(jiān)聽節(jié)點(diǎn)的狀態(tài)
- 如果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)檢測,主要處理邏輯如下:
- 調(diào)用sentinelReconnectInstance對實例的連接狀態(tài)進(jìn)行判斷,如果**連接中斷嘗試重新與實例建立連接 **
- 調(diào)用sentinelSendPeriodicCommands向?qū)嵗l(fā)送PING INFO等命令
- sentinelCheckSubjectivelyDown判斷實例是否主觀下線
- 如果實例是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)行重連,主要處理邏輯如下:
檢測連接是否中斷,如果未中斷直接返回
檢查端口是否為0,0被認(rèn)為是不合法的端口
-
從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; -
校驗距離上次重連時間是否小于PING的檢測周期SENTINEL_PING_PERIOD,如果小于說明距離上次重連時間過近,直接返回即可
SENTINEL_PING_PERIOD在server.c中定義,默認(rèn)1000毫秒
#define SENTINEL_PING_PERIOD 1000 對用于發(fā)送命令的連接判斷,如果連接為NULL,調(diào)用redisAsyncConnectBind函數(shù)進(jìn)行重連
對用于處理發(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é)

參考
極客時間 - Redis源碼剖析與實戰(zhàn)(蔣德鈞)
Redis版本:redis-6.2.5