這篇文章主要用來說述redis高可用的實現原理,學習redis高可用實現,可讓咱們更好的進行系統設計。也能夠學習redis中的設計,將其遷移到其餘系統的實現中去。redis
1.每10s,sentinel會發送info給全部data節點。算法
2.每2s,向_sentinel_:hello頻道發送主節點判斷以及當前sentinel節點的信息。服務器
<Sentinel節點IP> <Sentinel節點端口> <Sentinel節點runId> <Sentinel節點配置版本> <主節點名字> <主節點Ip> <主節點端口> <主節點配置版本>
複製代碼
3.每1s,向其餘節點發送ping(alive check)markdown
經過1和2,在sentinel啓動的時候只須要配置要監控的master,便可實現對其餘slave和sentinel的自動發現機制。主要依賴對redis data節點的發佈訂閱和info操做去作這個事情。網絡
主觀上判斷某個節點下線(在超時時間沒有ping經過)數據結構
若是主觀下線的是主節點,則會經過sentinel is- master-down-by-addr命令向其餘sentinel節點詢問對主節點的判斷。超高quorum數量,會進行客觀下線。架構
全部sentinel節點除了定時pub hello消息,還經過這個命令進行p2p通訊。及時告知其餘sentinel信息。 該命令做用:app
sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
複製代碼
若是runid爲*表明主節點下線命令運維
若是不爲*表明請求投票命令異步
主節點下線命令
sentinel is-master-down-by-addr 127.0.0.1 6379 0 *
複製代碼
領導選舉投票 使用raft的領主選舉算法,經過該命令投票選舉
命令返回結果
主節點故障,選舉從節點 1.選哪一個?
(1)過濾不健康的
(2)選slave-priority最高的從節點列表
(3)選擇複製偏移量最大的。
(4)選擇runid最小的
2.執行slaveof no one
3.向剩餘從節點發送命令,讓其成爲新master的從
4.將原來的主更新爲從。並保持對其關注
圖中每一個粉色的方框都表明一個SentinelRedisInstance,這是sentienl中統一的一個數據結構,簡單來說就是一塊內存,裏面存儲了節點的信息。每個redis data節點或者sentinel節點都會有一個SentinelRedisInstance(除了當前sentienl)。就比如每一條網絡鏈接都有一個socket同樣。
master表明監控的master信息。每一個sentinel有多個master(上面只畫了一個),表明能夠同時監控多個master節點。
SentinelRedisInstance結構有兩個hash:key爲名字,val爲SentinelRedisInstance。
一個用來存儲當前master的slave,另外一個用來存儲一樣監控這個master的其餘sentinel節點(如上圖結構),使用hash 是爲了經過key進行快速查找對應結構。
// 其餘一樣監控這個主服務器的全部 sentinel
dict *sentinels; /* Other sentinels monitoring the same master. */
// 若是這個實例表明的是一個主服務器
// 那麼這個字典保存着主服務器屬下的從服務器
// 字典的鍵是從服務器的名字,字典的值是從服務器對應的 sentinelRedisInstance 結構
dict *slaves; /* Slaves for this master instance. */
複製代碼
固然只有SentinelRedisInstance爲master的時候,這兩個hash纔會被用到。因此sentinel的全部操做都圍繞這個master結構進行。sentinel經過info命令以及publish hello消息發現其餘的slave和sentinel,新加入的節點會加入對應hash中。
flags
惟一須要強調的是SentinelRedisInstance中的flags變量。該變量記錄了當前節點的狀態。(flags經過標誌位記錄多種狀態,每一個狀態一個位,經過|~進行更新)
因此sentinel徹底圍繞這些SentinelRedisInstance去維護整個集羣的邏輯。好比ping,info,就是遍歷SentinelRedisInstance,對其hostport進行ping,info等操做。
遍歷邏輯:先拿到master的SentinelRedisInstance,執行邏輯,而後再遍歷兩個hash處理。
sentinel 模式啓動會執行下面邏輯
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
複製代碼
initSentinel主要就是一些初始化工做,包括sentinel結構建立、一些全局變量的初始化、以及註冊命令處理器。
服務啓動會先加載配置。這個方法主要是sentinel配置的解析邏輯。
if (!strcasecmp(argv[0],"monitor") && argc == 5) {
/* monitor <name> <host> <port> <quorum> */
// 讀入 quorum 參數
int quorum = atoi(argv[4]);
// 檢查 quorum 參數必須大於 0
if (quorum <= 0) return "Quorum must be 1 or greater.";
// 建立主服務器實例
if (createSentinelRedisInstance(argv[1],SRI_MASTER,argv[2],
atoi(argv[3]),quorum,NULL) == NULL)
{
switch(errno) {
case EBUSY: return "Duplicated master name.";
case ENOENT: return "Can't resolve master instance hostname.";
case EINVAL: return "Invalid port number";
}
}
}else if (!strcasecmp(argv[0],"down-after-milliseconds") && argc == 3) {
/* down-after-milliseconds <name> <milliseconds> */
// 查找主服務器
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
// 設置選項
ri->down_after_period = atoi(argv[2]);
if (ri->down_after_period <= 0)
return "negative or zero time parameter.";
sentinelPropagateDownAfterPeriod(ri);
}
複製代碼
核心就是對master的SentinelRedisInstance結構建立。而後將其餘配置初始化到該結構中。上面只列舉了部分代碼。其餘配置相似down-after-milliseconds的解析邏輯。
用來建立sentinel中實例。包括sentinel、被監控的redis的主從。每次有新增都會調用該方法建立一個實例,並加入到master的SentinelRedisInstance中(除了master自身)
上面基本就是一些初始化工做。sentinel核心邏輯是須要時間函數驅動的,因此咱們直接看sentinelTimer邏輯。
該函數100ms執行一次。
void sentinelTimer(void) {
// 記錄本次 sentinel 調用的事件,判斷是否須要進入 TITL 模式
sentinelCheckTiltCondition();
// 執行按期操做
// 好比 PING 實例、分析主服務器和從服務器的 INFO 命令
// 向其餘監視相同主服務器的 sentinel 發送問候信息
// 並接收其餘 sentinel 發來的問候信息
// 執行故障轉移操做,等等
sentinelHandleDictOfRedisInstances(sentinel.masters);
// 運行等待執行的腳本
sentinelRunPendingScripts();
// 清理已執行完畢的腳本,並重試出錯的腳本
sentinelCollectTerminatedScripts();
// 殺死運行超時的腳本
sentinelKillTimedoutScripts();
/* We continuously change the frequency of the Redis "timer interrupt" * in order to desynchronize every Sentinel from every other. * This non-determinism avoids that Sentinels started at the same time * exactly continue to stay synchronized asking to be voted at the * same time again and again (resulting in nobody likely winning the * election because of split brain voting). */
server.hz = REDIS_DEFAULT_HZ + rand() % REDIS_DEFAULT_HZ;
}
複製代碼
sentinelCheckTiltCondition方法用來判斷是否進入ttl,而且記錄執行時間。
TITL模式:由於sentinel依賴本機時間驅動,若是系統時間出問題,或者由於進程阻塞致使的時間函數延遲調用。這時再去參與集羣邏輯會出現不正確的決策。所以若是當前時間和上一次執行時間差爲負值或者超過2s,該節點會進入TILT模式。
void sentinelHandleDictOfRedisInstances(dict *instances) {
dictIterator *di;
dictEntry *de;
sentinelRedisInstance *switch_to_promoted = NULL;
/* There are a number of things we need to perform against every master. */
// 遍歷多個實例,這些實例能夠是多個主服務器、多個從服務器或者多個 sentinel
di = dictGetIterator(instances);
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
// 執行調度操做
sentinelHandleRedisInstance(ri);
// 若是被遍歷的是主服務器,那麼遞歸地遍歷該主服務器的全部從服務器
if (ri->flags & SRI_MASTER) {
// 全部從服務器
sentinelHandleDictOfRedisInstances(ri->slaves);
// 全部 sentinel
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);
}
複製代碼
這個方法就遞歸遍歷全部SentinelRedisInstance。。核心就是對被 Sentinel 監視的全部實例(包括主服務器、從服務器和其餘 Sentinel ) 進行按期操做。
邏輯很簡單,其實就是執行sentinelHandleRedisInstance方法。
若是有故障轉移(從節點升級爲主節點),則調用sentinelFailoverSwitchToPromotedSlave替換新主服務。
Sentinel的主邏輯流程,後面會一一介紹每一個方法的邏輯。該方法按期調用,非阻塞(中間的io命令都會異步出去)。
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
/* Every kind of instance */
// 若是有須要的話,建立連向實例的網絡鏈接
sentinelReconnectInstance(ri);
// 根據狀況,向實例發送 PING、 INFO 或者 PUBLISH 命令
sentinelSendPeriodicCommands(ri);
/* ============== ACTING HALF ============= */
/* We don't proceed with the acting half if we are in TILT mode. * TILT happens when we find something odd with the time, like a * sudden change in the clock. */
if (sentinel.tilt) {
// 若是 TILI 模式未解除,那麼不執行動做
if (mstime()-sentinel.tilt_start_time < SENTINEL_TILT_PERIOD) return;
// 時間已過,退出 TILT 模式
sentinel.tilt = 0;
sentinelEvent(REDIS_WARNING,"-tilt",NULL,"#tilt mode exited");
}
/* Every kind of instance */
// 檢查給定實例是否進入 SDOWN 狀態
sentinelCheckSubjectivelyDown(ri);
/* Masters and slaves */
if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
/* Nothing so far. */
}
/* Only masters */
/* 對主服務器進行處理 */
if (ri->flags & SRI_MASTER) {
// 判斷 master 是否進入 ODOWN 狀態
sentinelCheckObjectivelyDown(ri);
// 若是主服務器進入了 ODOWN 狀態,那麼開始一次故障轉移操做
if (sentinelStartFailoverIfNeeded(ri))
// 強制向其餘 Sentinel 發送 SENTINEL is-master-down-by-addr 命令
// 刷新其餘 Sentinel 關於主服務器的狀態
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
// 執行故障轉移
sentinelFailoverStateMachine(ri);
// 若是有須要的話,向其餘 Sentinel 發送 SENTINEL is-master-down-by-addr 命令
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
}
}
複製代碼
1.sentinelReconnectInstance主要就是建立和實例的鏈接。若是是master或者slave實例,則訂閱對應節點的__sentinel__:hello頻道,用來接受其餘sentinel廣播的消息。每一個sentinel會按期給該頻道publish。 由於啓動的時候只是初始化了實例數據,並無建立鏈接。對於每個實例,都是在這裏去建立鏈接的。
2.sentinelSendPeriodicCommands主要發送ping、info、publish命令。
3.若是當前在tilt模式中,直接返回,非tilt模式纔會執行後續操做。
4.sentinelCheckSubjectivelyDown,檢查給定實例是否進入 SDOWN 狀態
5.若是當前實例爲主服務器,則執行一些故障判斷和故障轉移操做。
能夠看出來sentienl的全部邏輯都是圍繞一個主服務進行的。
這是咱們的核心關注點,sentinel之間的通訊邏輯。
這個方法主要是ping、info以及publish hello消息。到達執行時刻會執行對應邏輯。
redis經過pending_commands記錄當前正在異步執行的命令。若是超過100,則再也不發送,避免命令堆積。由於sentinel是依賴上一次響應時間來判斷是否發送命令,若是出現網絡阻塞或者波動,會致使頻繁發送。
發送邏輯在sentinelSendPeriodicCommands方法中。
Ping命令回調方法sentinelPingReplyCallback:,若是回調正常,更新對應字段(last_avail_time、last_pong_time),sentienl依賴這些字段進行判活。若是節點執行lua超時,則調用SCRIPT KILL嘗試殺死腳本。
info爲獲取redis節點信息的命令。處理返回結果的方法爲sentinelRefreshInstanceInfo
sentinelRefreshInstanceInfo方法主要是解析並提取須要的數據信息。下面介紹一些核心信息。
1.若是是主節點,提取其從節點的hostport,併爲從節點建立實例信息(若是是新發現的),sentinel依賴此方式發現其餘的slave節點。
if (sentinelRedisInstanceLookupSlave(ri,ip,atoi(port)) == NULL) {
if ((slave = createSentinelRedisInstance(NULL,SRI_SLAVE,ip,
atoi(port), ri->quorum, ri)) != NULL)
{
sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@");
}
}
複製代碼
2.若是是從節點,記錄當前從服務器對應主節點的信息。由於sentinel的記錄的主節點不必定是正確的(網絡分區致使切換延遲),因此經過info獲取到該從服務器最新信息。以供後面邏輯處理。
這裏其實兩種主(從)節點狀態:一種是sentienl認爲當前節點爲主(從)節點,另外一種是當前節點認爲的主(從)節點。
3.若是發生了角色轉變(info返回的和當前sentinel記錄的節點狀態不一致),更新轉變時間。若是tilt,直接返回。不然,根據該節點返回的role和sentinel記錄的role進行一些邏輯,具體邏輯咱們後面再研究。
給對應channel發送的信息以下,主要包括對應sentinel的信息和當前master的信息
snprintf(payload,sizeof(payload),
"%s,%d,%s,%llu," /* Info about this sentinel. */
"%s,%s,%d,%llu", /* Info about current master. */
ip, server.port, server.runid,
(unsigned long long) sentinel.current_epoch,
/* --- */
master->name,master_addr->ip,master_addr->port,
(unsigned long long) master->config_epoch);
複製代碼
該方法爲sentinel處理hello信息的方法。
1.若是hello消息中sentinel節點爲新發現的節點(當前setinel不存在的),爲新節點建立實例,加入到列表中。這是sentinel發現其餘sentinel的惟一方式。
2.更新current_epoch=max(當前current_epoch,hello的current_epoch)
3.若是hello消息的master_config_epoch比該節點master的config_epoch大。則調用sentinelResetMasterAndChangeAddress方法切換當前master。config_epoch爲故障轉移使用的紀元。故障轉移以後會遞增。若是發現比較大的,說明進行了故障轉移,則信任hello中的master爲最新master
if (master->config_epoch < master_config_epoch) {
master->config_epoch = master_config_epoch;
if (master_port != master->addr->port ||
strcmp(master->addr->ip, token[5]))
{
sentinelAddr *old_addr;
sentinelEvent(REDIS_WARNING,"+config-update-from",si,"%@");
sentinelEvent(REDIS_WARNING,"+switch-master",
master,"%s %s %d %s %d",
master->name,
master->addr->ip, master->addr->port,
token[5], master_port);
old_addr = dupSentinelAddr(master->addr);
sentinelResetMasterAndChangeAddress(master, token[5], master_port);
sentinelCallClientReconfScript(master,
SENTINEL_OBSERVER,"start",
old_addr,master->addr);
releaseSentinelAddr(old_addr);
}
}
複製代碼
檢查實例是否進入 SDOWN 狀態
1.若是實例符合斷線重連的條件,則斷開該實例鏈接,等待下次從新鏈接。其實就是對不活躍實例進行斷線重連。
2.設置或者取消SDOWN標誌
達到下面兩個條件設置爲SDOWN,不然取消SDOWN標誌位
if (ri->flags & SRI_MASTER) {
// 判斷 master 是否進入 ODOWN 狀態
sentinelCheckObjectivelyDown(ri);
// 若是主服務器進入了 ODOWN 狀態,那麼開始一次故障轉移操做
if (sentinelStartFailoverIfNeeded(ri))
// 強制向其餘 Sentinel 發送 SENTINEL is-master-down-by-addr 命令
// 刷新其餘 Sentinel 關於主服務器的狀態
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
// 執行故障轉移
sentinelFailoverStateMachine(ri);
// 若是有須要的話,向其餘 Sentinel 發送 SENTINEL is-master-down-by-addr 命令
// 刷新其餘 Sentinel 關於主服務器的狀態
// 這一句是對那些沒有進入 if(sentinelStartFailoverIfNeeded(ri)) { /* ... */ }
// 語句的主服務器使用的
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
}
複製代碼
判斷當前主節點是否進入ODown狀態。
經過遍歷全部sentinel實例的flags標誌位進行判斷。若是一半以上主觀下線,則變動爲客觀下線。這個狀態位是在is-master-down-by-addr命令回調中更新的。
判斷是否須要進行故障轉移
void sentinelStartFailover(sentinelRedisInstance *master) {
redisAssert(master->flags & SRI_MASTER);
// 更新故障轉移狀態
master->failover_state = SENTINEL_FAILOVER_STATE_WAIT_START;
// 更新主服務器狀態
master->flags |= SRI_FAILOVER_IN_PROGRESS;
// 更新紀元
master->failover_epoch = ++sentinel.current_epoch;
sentinelEvent(REDIS_WARNING,"+new-epoch",master,"%llu",
(unsigned long long) sentinel.current_epoch);
sentinelEvent(REDIS_WARNING,"+try-failover",master,"%@");
// 記錄故障轉移狀態的變動時間
master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC;
master->failover_state_change_time = mstime();
}
複製代碼
判斷條件故障轉移:
1.進入ODOWN而且沒有在故障轉移中。
2.若是發現故障轉移過於頻繁也不執行。
若是須要故障轉移,則更新當前master的信息,主要是failover_state、failover_epoch等字段。
failover_epoch爲當前master紀元+1。
failover_epoch做用:
- 其餘sentinel依賴這個字段判斷是否須要進行故障轉移。這個在以前的hello中有說到。
- 當前sentinel依賴這個紀元選出執行故障轉移的leader。由於選舉使用的也是該紀元。選舉出來的leader的紀元應該一致。每次選舉都會產生一個新的leader,最新的紀元最權威,這是raft領導選舉的核心概念。雖然raft使用的是term。
failover_state表明當前故障轉移狀態。故障轉移操做須要依賴該狀態。
故障轉移操做須要從sentienl中選舉一個執行。因此這只是先更新狀態。
向其餘sentine詢問master狀態。
這會遍歷全部sentinel,若是當前sentinel認爲master下線,而且鏈接正常,會發送is-master-down-by-addr命令
sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
複製代碼
若是本sentinel檢測到master 主觀下線(經過failover_state判斷),則runid爲當前server的runid,表明讓其餘sentienl給本身投票。
若是是master客觀下線,則runid=*,表明告訴其餘sentienl,主節點下線。這是sentinel通知其餘sentinel主節點下線的惟一方式。
該邏輯在sentinelCommand中執行
ri = getSentinelRedisInstanceByAddrAndRunID(sentinel.masters,
c->argv[2]->ptr,port,NULL);
if (!sentinel.tilt && ri && (ri->flags & SRI_S_DOWN) &&
(ri->flags & SRI_MASTER))
isdown = 1;
/* Vote for the master (or fetch the previous vote) if the request * includes a runid, otherwise the sender is not seeking for a vote. */
if (ri && ri->flags & SRI_MASTER && strcasecmp(c->argv[5]->ptr,"*")) {
leader = sentinelVoteLeader(ri,(uint64_t)req_epoch,
c->argv[5]->ptr,
&leader_epoch);
}
/* Reply with a three-elements multi-bulk reply: * down state, leader, vote epoch. */
// 多條回覆
// 1) <down_state> 1 表明下線, 0 表明未下線
// 2) <leader_runid> Sentinel 選舉做爲領頭 Sentinel 的運行 ID
// 3) <leader_epoch> 領頭 Sentinel 目前的配置紀元
addReplyMultiBulkLen(c,3);
addReply(c, isdown ? shared.cone : shared.czero);
addReplyBulkCString(c, leader ? leader : "*");
addReplyLongLong(c, (long long)leader_epoch);
複製代碼
若是節點下線回覆down_state1,不然爲0
根據ip和port獲取ri(sentinelRedisInstance),若是ri是主節點,而且runid不爲*,則進行選舉投票。
投票邏輯:
請求的req_epoch>當前sentinel的current_epoch(更新sentinel的current_epoch)
req_epoch>master的master.leader_epoch,而且>=sentinel.current_epoch,更新master的leader爲req_runid。而後投票給當前req_runid節點。
其實就是判斷epoch。若是請求的epoch比較大,那就投票便可。和raft的領導選舉同樣。
// 更新最後一次回覆詢問的時間
ri->last_master_down_reply_time = mstime();
// 設置 SENTINEL 認爲主服務器的狀態
if (r->element[0]->integer == 1) {
// 已下線
ri->flags |= SRI_MASTER_DOWN;
} else {
// 未下線
ri->flags &= ~SRI_MASTER_DOWN;
}
// 若是運行 ID 不是 "*" 的話,那麼這是一個帶投票的回覆
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 (ri->leader_epoch != r->element[2]->integer)
redisLog(REDIS_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;
}
複製代碼
1.更新回覆時間
2.更新被詢問的sentinel的flag。用於ODOWN判斷。
3.若是leader_runid非*,表明投票信息,更新該sentinel的leader信息。
ri的leader字段有兩個狀態:
switch(ri->failover_state) {
// 等待故障轉移開始
case SENTINEL_FAILOVER_STATE_WAIT_START:
sentinelFailoverWaitStart(ri);
break;
// 選擇新主服務器
case SENTINEL_FAILOVER_STATE_SELECT_SLAVE:
sentinelFailoverSelectSlave(ri);
break;
// 升級被選中的從服務器爲新主服務器
case SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE:
sentinelFailoverSendSlaveOfNoOne(ri);
break;
// 等待升級生效,若是升級超時,那麼從新選擇新主服務器
// 具體狀況請看 sentinelRefreshInstanceInfo 函數
case SENTINEL_FAILOVER_STATE_WAIT_PROMOTION:
sentinelFailoverWaitPromotion(ri);
break;
// 向從服務器發送 SLAVEOF 命令,讓它們同步新主服務器
case SENTINEL_FAILOVER_STATE_RECONF_SLAVES:
sentinelFailoverReconfNextSlave(ri);
break;
}
複製代碼
整個流程如上:
1.sentinelFailoverWaitStart會根據當前全部sentinel的leader以及當前故障轉移紀元選出來leader。若是發現leader是本身,則切換failover_state爲SENTINEL_FAILOVER_STATE_SELECT_SLAVE,執行下一個case。不然跳出。
2.根據邏輯選舉從服務器做爲新的主節點,若是沒有選出,清空故障轉移狀態。選出成功後繼續更新狀態執行下一個case。選擇邏輯上面有說過。
3.升級新節點爲主節點,若是發現斷線並超時,則終止故障轉移邏輯。這個其實就是異步發送slave of命令。讓從節點升級爲主節點。這個命令在redis複製源碼解析中說過。
4.SENTINEL_FAILOVER_STATE_WAIT_PROMOTION狀態只是等待升級的另外一個邏輯,若是升級超時則終止故障轉移。 如何檢測從節點升級主節點成功?
其實在info中有一段邏輯:
if ((ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
(ri->master->failover_state ==
SENTINEL_FAILOVER_STATE_WAIT_PROMOTION))
{
// 更新從服務器的主服務器(已下線)的配置紀元
ri->master->config_epoch = ri->master->failover_epoch;
// 設置從服務器的主服務器(已下線)的故障轉移狀態
// 這個狀態會讓從服務器開始同步新的主服務器
ri->master->failover_state = SENTINEL_FAILOVER_STATE_RECONF_SLAVES;
// 更新從服務器的主服務器(已下線)的故障轉移狀態變動時間
ri->master->failover_state_change_time = mstime();
// 將當前 Sentinel 狀態保存到配置文件裏面
sentinelFlushConfig();
// 發送事件
sentinelEvent(REDIS_WARNING,"+promoted-slave",ri,"%@");
sentinelEvent(REDIS_WARNING,"+failover-state-reconf-slaves",
ri->master,"%@");
// 執行腳本
sentinelCallClientReconfScript(ri->master,SENTINEL_LEADER,
"start",ri->master->addr,ri->addr);
}
複製代碼
在這裏表明當前從節點已經升級成功,則更新config_epoch。config_epoch其實在hello消息中被用到。若是其餘sentienl發現它的config_epoch小於hello消息中的config_epoch,則會重置master的地址。
5.SENTINEL_FAILOVER_STATE_RECONF_SLAVES
Info更新成功後,會執行sentinelFailoverReconfNextSlave方法。其實就是向全部從服務器發送slave of命令。 這裏會受parallel_syncs參數限制。控制並行slave of數量,避免主節點網絡壓力。
成功以後更新failover_state狀態爲SENTINEL_FAILOVER_STATE_UPDATE_CONFIG
最終會調用該方法切換主服務器,整個轉移過程結束。
若是執行故障轉移的leader存活的狀況,轉移超時,則會調用sentinelAbortFailover方法終止故障轉移。 若是轉移過場中leader宕機,其餘節點會繼續執行故障轉移邏輯。
down-after-milliseconds 節點alive check的ttl
sentinel parallel-syncs 主節點宕機,容許並行複製數量
sentinel failover-timeout 故障轉移的超時時間,若是當前時間-上次轉移狀態更新時間大於該值,則會終止轉移。
sentinel notification-script 故障監控,sentinel警告級別的事件發生,會出發對應路徑下的腳本,經過腳本可接收參數。進行監控。
啓動經過遍歷sentinel獲取redis主節點。而後訂閱每一個sentinel的switch事件,保證在主備切換的時候能監聽到。若是監聽到變化 從新初始化鏈接池便可。
須要注意的是,只有故障轉移完成纔會發送此事件。
整個實現比較複雜,可是按照每一個點仍是能夠理清楚。經過ping、info、以及pubsub hello消息實現通訊。保證整個系統的一致性。每次故障轉移只會由一個sentinel執行,這個選舉過程依賴raft算法leader選舉邏輯。故障轉移邏輯依賴超時時間避免死狀態。整個邏輯依賴狀態機進行切換,有條不紊,值得借鑑。