【Redis 源碼】集羣之哨兵 sentinel 初識
前言:
說到哨兵之前,可以思考一個問題。爲什麼有哨兵機制?這一點其實可以從主從複製的優缺點作爲考慮。主從複製其實有一個致命的弱點就是它是非高可用的,比如說當主節點出現故障時,那麼此時我的寫入就會出現問題,切換需要重新配,就無法做到高可用。此時其實就可以用到哨兵進制,哨兵自動發現故障,並通過選舉算法重新選舉新主完成轉移和通知實現高可用。
參考資料:
https://redis.io/topics/sentinel
一篇看着太長分爲兩篇:
《集羣之哨兵 sentinel 初識》和《集羣之哨兵 sentinel 故障轉移》。
(一)理解哨兵模式
基礎結構
-
哨兵本身是監聽者身份,沒有存儲功能。在整體體系當中,一個 sentinel 或者一個羣 sentinels 與 redis 主從體系是有監聽和被監聽者關係。
-
哨兵有如下一些功能:監聽、通知、自動故障轉移、配置提供程序。
1)監聽:哨兵會不斷檢查主服務和從服務是否正常運行。
2)通知:哨兵可以通過 API 通知到系統管理員或者其他程序,其中一個受監聽 redis 實例出問題。
3)自動轉移故障:如果主服務不正常,則哨兵可以啓動故障轉移,在該從服務升級爲主服務。將其該主服務下其他從服務重新配置主服務,並通知使用 Redis 服務器的應用要使用新地址。
- 配置提供程序: Sentinel 充當客戶端服務發現的授權來源, 客戶端連接到 Sentinels,以詢問負責給定服務的當前 Redis 主服務器的地址。如果發生故障轉移,Sentinels 將報告新地址。
-
哨兵存在三種交互模式:哨兵與主服務、哨兵與從服務、哨兵與其他哨兵。
-
哨兵體現中有三個角色:master、slave、sentinel。
(二)配置哨兵模
1.1 sentinel.conf 配置說明
# 哨兵sentinel監控的redis主節點的 ip port
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 當在Redis實例中開啓了requirepass foobared 授權密碼 這樣所有連接Redis實例的客戶端都要提供密碼
# 設置哨兵sentinel 連接主從的密碼 注意必須爲主從設置一樣的驗證密碼
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之後 主節點沒有應答哨兵sentinel 此時 哨兵主觀上認爲主節點下線 默認30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 這個配置項指定了在發生failover主備切換時最多可以有多少個slave同時對新的master進行 同步,
# 這個數字越小,完成failover所需的時間就越長,
# 但是如果這個數字越大,就意味着越 多的slave因爲replication而不可用。
# 可以通過將這個值設爲 1 來保證每次只有一個slave 處於不能處理命令請求的狀態。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障轉移的超時時間 failover-timeout 可以用在以下這些方面:
# 1. 同一個sentinel對同一個master兩次failover之間的間隔時間。
# 2. 當一個slave從一個錯誤的master那裏同步數據開始計算時間。直到slave被糾正爲向正確的master那裏同步數據時。
# 3.當想要取消一個正在進行的failover所需要的時間。
# 4.當進行failover時,配置所有slaves指向新的master所需的最大時間。不過,即使過了這個超時,slaves依然會被正確配置爲指向master,
# 但是就不按parallel-syncs所配置的規則來了
# 默認三分鐘
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# 哨兵sentinel實例運行的端口 默認26379
port 26379
# SCRIPTS EXECUTION
# 配置當某一事件發生時所需要執行的腳本,可以通過腳本來通知管理員,例如當系統運行不正常時發郵件通知相關人員。
# 對於腳本的運行結果有以下規則:
# 若腳本執行後返回1,那麼該腳本稍後將會被再次執行,重複次數目前默認爲10
# 若腳本執行後返回2,或者比2更高的一個返回值,腳本將不會重複執行。
# 如果腳本在執行過程中由於收到系統中斷信號被終止了,則同返回值爲1時的行爲相同。
# 一個腳本的最大執行時間爲60s,如果超過這個時間,腳本將會被一個SIGKILL信號終止,之後重新執行。
# 通知型腳本:當sentinel有任何警告級別的事件發生時(比如說redis實例的主觀失效和客觀失效等等),將會去調用這個腳本,
# 這時這個腳本應該通過郵件,SMS等方式去通知系統管理員關於系統不正常運行的信息。調用該腳本時,將傳給腳本兩個參數,
# 一個是事件的類型,一個是事件的描述。如果sentinel.conf配置文件中配置了這個腳本路徑,那麼必須保證這個腳本存在於這個
# 路徑,並且是可執行的,否則sentinel無法正常啓動成功。
# 通知腳本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客戶端重新配置主節點參數腳本
# 當一個master由於failover而發生改變時,這個腳本將會被調用,通知相關的客戶端關於master地址已經發生改變的信息。
# 以下參數將會在調用腳本時傳給腳本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>總是“failover”,
# <role>是“leader”或者“observer”中的一個。
# 參數 from-ip, from-port, to-ip, to-port是用來和舊的master和新的master(即舊的slave)通信的
# 這個腳本應該是通用的,能被多次調用,不是針對性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
master-name 節點名稱,名稱只能是 [A-z 0-9] 以及.-_內的字符。
quorum 當這些 quorum 個數 sentinel 哨兵認爲 master 主節點失聯, 那麼這時客觀上認爲主節點失聯了 。
1.2 啓動哨兵模式
如果使用的是 redis-sentinel 可執行文件(或者您具有指向該 redis-server 可執行文件的名稱的符號鏈接),則可以使用以下命令行運行 Sentinel:
redis-sentinel /path/to/sentinel.conf
否則,您可以直接使用 redis-server 在 Sentinel 模式下啓動的可執行文件:
redis-server /path/to/sentinel.conf --sentinel
server.c 源碼中可以體現兩種加載模式:
int checkForSentinelMode(int argc, char **argv) {
int j;
if (strstr(argv[0],"redis-sentinel") != NULL) return 1;
for (j = 1; j < argc; j++)
if (!strcmp(argv[j],"--sentinel")) return 1;
return 0;
}
(三)哨兵源碼解析
2.1 初始化哨兵
1) server.c 哨兵初始化
int main(int argc, char **argv) {
//...省略
server.sentinel_mode = checkForSentinelMode(argc,argv); //判斷是否爲哨兵模式
//...省略
if (server.sentinel_mode) {
initSentinelConfig(); //加載端口
initSentinel(); //初始化哨兵模式,包括命令調用和各種數據結構。
}
//...省略
if (!server.sentinel_mode) { //普通模式
//...省略
} else {
/*
檢測哨兵模式是否正常配置
*/
sentinelIsRunning();
}
}
initSentinelConfig 函數中爲加載哨兵端口,默認端口爲 26379。initSentinel 函數中初始化哨兵模式,包括命令調用和各種數據結構。
initSentinel 函數如下:
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},
{"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
};
void initSentinel(void) {
unsigned int j;
/* 初始化哨兵命令 */
dictEmpty(server.commands,NULL);
for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) {
int retval;
struct redisCommand *cmd = sentinelcmds+j;
retval = dictAdd(server.commands, sdsnew(cmd->name), cmd);
serverAssert(retval == DICT_OK);
}
/* 初始化各種數據結構. */
sentinel.current_epoch = 0;
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;
memset(sentinel.myid,0,sizeof(sentinel.myid));
}
redisCommand 結構體數組爲哨兵模式支持命令集。哨兵有單獨的命令集,只支持 ping、sentinel、subscribe、unsubscribe、psubscribe、publish、info、role、client、shutdown 命令。
2) server.c 哨兵任務
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
//...省略
run_with_period(100) {
if (server.sentinel_mode) sentinelTimer(); //哨兵相關模式下運行任務
}
//...省略
}
serverCron 爲 initServer 函數中的註冊時間回調。serverCron 中調用 sentinelTimer 方法中執行哨兵模式中的任務。
包括執行定期操作比如 PING、分析主服務和從服務的 INFO 命令、故障轉移等等。
2.2 定期操作
週期執行命令函數 sentinelSendPeriodicCommands
void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
mstime_t now = mstime();
mstime_t info_period, ping_period;
int retval;
//。。。省略
/* 如果這是一個處於O_DOWN狀態的主服務器的從服務器,我們開始發送
它每秒鐘都有信息,而不是通常的哨兵信息期週期 */
if ((ri->flags & SRI_SLAVE) &&
((ri->master->flags & (SRI_O_DOWN|SRI_FAILOVER_IN_PROGRESS)) ||
(ri->master_link_down_time != 0)))
{
info_period = 1000; //1s
} else {
info_period = SENTINEL_INFO_PERIOD; //10s
}
/* ping通過 down-after-milliseconds 參數可以配置,默認1秒*/
ping_period = ri->down_after_period;
if (ping_period > SENTINEL_PING_PERIOD) ping_period = SENTINEL_PING_PERIOD;
/* Send INFO to masters and slaves, not sentinels. */
if ((ri->flags & SRI_SENTINEL) == 0 &&
(ri->info_refresh == 0 ||
(now - ri->info_refresh) > info_period))
{
retval = redisAsyncCommand(ri->link->cc,
sentinelInfoReplyCallback, ri, "%s",
sentinelInstanceMapCommand(ri,"INFO"));
if (retval == C_OK) ri->link->pending_commands++;
}
/* 發送ping */
if ((now - ri->link->last_pong_time) > ping_period &&
(now - ri->link->last_ping_time) > ping_period/2) {
sentinelSendPing(ri);
}
/* PUBLISH 發送hello,SENTINEL_PUBLISH_PERIOD爲2000則爲2s */
if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
sentinelSendHello(ri);
}
}
正常情況下每 10 秒會獲取一次 info 主和從到。每 1 秒發送 ping 命令,ping 命令 down-after-milliseconds 可以配置,每 2 秒廣播一次 hello msg。
總結:
(1)哨兵沒有存儲功能。
(2)哨兵服務支持命令:ping、sentinel、subscribe、unsubscribe、psubscribe、publish、info、role、client、shutdown
(3)哨兵提供功能:監聽、通知、自動故障轉移、配置提供程序。
(4)哨兵有兩種啓動模式:配置或者命令行參數。
(5)哨兵體現中有三個角色:master、slave、sentinel。
(6)週期函數主從非 O_DOWN 狀態下每 10s 發送一次 info 命令到主從。
(7)ping 命令默認是 1s 發送一次,可以通過 down-after-milliseconds 配置。
(8)每 2 秒廣播一次 hello msg。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/WkH0UOwwnaUmVrS3TPnieQ