redis在啟動時,如果進(jìn)程名是"redis-sentinel",或者參數(shù)中帶了"--sentinel",這時redis便以哨兵的方式運行。一個sentinel可以監(jiān)控多個master。
sentinel的配置如下
// 當(dāng)前Sentinel節(jié)點監(jiān)控 127.0.0.1:6379 這個主節(jié)點
// 2代表判斷主節(jié)點失敗至少需要2個Sentinel節(jié)點節(jié)點同意
// mymaster是主節(jié)點的別名
sentinel monitor mymaster 127.0.0.1 6379 2
//每個Sentinel節(jié)點都要定期PING命令來判斷Redis數(shù)據(jù)節(jié)點和其余Sentinel節(jié)點是否可達(dá),如果超過30000毫秒且沒有回復(fù),則判定不可達(dá)
sentinel down-after-milliseconds mymaster 30000
//當(dāng)Sentinel節(jié)點集合對主節(jié)點故障判定達(dá)成一致時,Sentinel領(lǐng)導(dǎo)者節(jié)點會做故障轉(zhuǎn)移操作,選出新的主節(jié)點,原來的從節(jié)點會向新的主節(jié)點發(fā)起復(fù)制操作,限制每次向新的主節(jié)點發(fā)起復(fù)制操作的從節(jié)點個數(shù)為1
sentinel parallel-syncs mymaster 1
//故障轉(zhuǎn)移超時時間為180000毫秒
sentinel failover-timeout mymaster 180000
sentinel支持的如下的命令列表:
struct redisCommand sentinelcmds[] = {
{"ping",pingCommand,1,"",0,NULL,0,0,0,0,0},
{"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0},
{"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0},
{"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
};
sentinel的時間事件也區(qū)別于一般服務(wù),入口是sentinelTimer。
void sentinelTimer(void) {
// 記錄本次 sentinel 調(diào)用的事件,
// 并判斷是否需要進(jìn)入 TITL 模式
sentinelCheckTiltCondition();
// 執(zhí)行定期操作
// 比如 PING 實例、分析主服務(wù)器和從服務(wù)器的 INFO 命令
// 向其他監(jiān)視相同主服務(wù)器的 sentinel 發(fā)送問候信息
// 并接收其他 sentinel 發(fā)來的問候信息
// 執(zhí)行故障轉(zhuǎn)移操作,等等
sentinelHandleDictOfRedisInstances(sentinel.masters);
// 運行等待執(zhí)行的腳本
sentinelRunPendingScripts();
// 清理已執(zhí)行完畢的腳本,并重試出錯的腳本
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;
}
一次時間事件中,主要做了以下幾件事情。
1.判斷是否上次執(zhí)行時間事件距今是否差了超過了2秒,則進(jìn)入默認(rèn)持續(xù)30秒的TITL模式,進(jìn)入了此模式的sentinel認(rèn)為當(dāng)前自身的狀態(tài)可能是有問題的,會閹割一部分選舉master相關(guān)的功能。
2.連接其他master/slave/sentinel,發(fā)送auth命令,master的地址是配置文件配好的,所以可以直接拿到;slave和其他sentinel的地址通過master拿到。
3.向master和slave定期發(fā)送 ping,info命令。其中info命令可以得到對方的運行狀態(tài),主從關(guān)系等。
4.哨兵在連接建立的時候會向master和其slave訂閱頻道"_sentinel_:hello"的消息,同時周期性的發(fā)布一條hello消息,給其他哨兵打招呼,這條消息包含了自身地址,自身runid,自身的epoch,master的基本信息和epoch。
假設(shè)有個哨兵B也是檢測這個master的,會接受到A發(fā)送的推送,把A保存到自己的哨兵列表中。5.根據(jù)redis服務(wù)回復(fù)ping的情況,判斷redis是否已經(jīng)主觀下線(Subject Down),如果下線了,將結(jié)果告知給其他sentinel。
6.如果master主觀下線了,判斷這個master是否客觀下線(Object Down),方法就是收集其他sentinel的意見,如果認(rèn)為該master下線的sentinel數(shù)量大于配置,則認(rèn)為這個master下線了,此時開始執(zhí)行故障轉(zhuǎn)移邏輯(Failover)。
-
7.故障轉(zhuǎn)移,分為以下步驟:
1)選舉頭領(lǐng)(leader)
1-1)首先向其他sentinel發(fā)送紀(jì)元編號(為本sentinel自身當(dāng)前的紀(jì)元自增1,同時記錄為故障master的紀(jì)元),如果其他紀(jì)元收到了這條消息,如果比自己的大,則更新為此紀(jì)元,否則忽略;
1-2)統(tǒng)計這個故障master下的所有sentinel投票結(jié)果,如果已經(jīng)有其他sentinel發(fā)表意見了,則選取最高得票者,投他一票,如果沒有,投自己一票。此時,如果最高得票者的得票數(shù)超過半數(shù)+1,則本次選舉完成,否則進(jìn)入下個紀(jì)元。引入紀(jì)元(epoch)這個參數(shù)是因為,選舉不是每次都成功的,比如所有sentinel都選舉了自己,這種情況下需要重新啟動下一輪投票。
2)由leader從slave當(dāng)中選擇一個作為新的master。
2-1) 過濾掉Down掉的slave,判斷slave的Info回復(fù)時長和與master的連接狀態(tài),太差的slave不考慮。
2-2) 對符合條件的slave排序以選出最優(yōu),依據(jù)分別是slave的優(yōu)先級,從原master復(fù)制的數(shù)據(jù)量大小,runid。- 向選出來的slave發(fā)送 slaveof no one來提升該slave為master。確定該服務(wù)提升為master之后,給其他slave發(fā)送slaveof命令。
sentinel內(nèi)部狀態(tài)發(fā)生變化時,會調(diào)用sentinelEvent方法廣播狀態(tài),我們可以使用redis-cli連接sentinel,用subscribe來查看sentinel的內(nèi)部運行狀態(tài),可以訂閱多個狀態(tài),用空格隔開,本機測試:
localhost:Debug jjchen$ redis-cli -p 26379
redis> subscribe +tilt -tilt
Reading messages... (press Ctrl-c to quit)
1. "subscribe"
2. "+tilt"
3. (integer) 1
1. "subscribe"
2. "-tilt"
3. (integer) 2
1. "message"
2. "+tilt"
3. "#tilt mode entered"
1. "message"
2. "-tilt"
3. "#tilt mode exited"
1. "message"
2. "+tilt"
3. "#tilt mode entered"