Redis Sentinel原始碼分析(一)
在程式碼分析前,先總體介紹下sentinel 的機制。
1. 下線定義
sentinel對下線有兩種定義:
a.主觀下線(sdown):sentinel例項本身對服務例項的判斷
b.客觀下線(odown):多個sentinel例項對同一個服務SDOWN的狀態做出協商後的判斷,只有master才可能在odown狀態
簡單的說,一個sentinel單獨做出的判斷只能是sdown,是沒有任何官方效力的,只有多個sentinel大家商量好,得到一致,才能將某個master狀態置為odown,只有確定master odown狀態後,才能做後續fail over的操作
2. 通訊
sentinel與maste/slave的互動主要包括:
a.PING:sentinel向其傳送PING以瞭解其狀態(是否下線)
b.INFO:sentinel向其傳送INFO以獲取replication相關的資訊
c.PUBLISH:sentinel向其監控的master/slave釋出本身的資訊及master相關的配置
d.SUBSCRIBE:sentinel通過訂閱master/slave的”__sentinel__:hello“頻道以獲取其它正在監控相同服務的sentinel
sentinel與sentinel的互動主要包括:
a.PING:sentinel向slave傳送PING以瞭解其狀態(是否下線)
b.SENTINEL is-master-down-by-addr:和其他sentinel協商master狀態,如果master odown,則投票選出leader做fail over
3. fail over
一次完整的fail over包括以下步驟:
a. sentinel發現master下線,則標記master sdown
b. 和其他sentinel協商以確定master狀態是否odown
c. 如果master odown,則選出leader
d. 當選為leader的sentinel選出一個slave做為master,並向該slave傳送slaveof no one命令以轉變slave角色為master
e. 向已下線的master及其他slave傳送slaveof xxxx命令使其作為新當選master的slave
本篇介紹Redis Sentinel初始化及定時事件註冊相關的程式碼,監控&fail over相關的程式碼在下一篇中介紹
redis sentinle可以執行./redis-sentinel sentinel.conf或./redis-server --sentinel sentinel.con執行sentinel程式
其初始化流程和redis-server執行一致,程式初始化會判斷是否處於sentinel模式,如果處於sentinel模式,則會完成一些sentinel相關的初始化工作,主要包括:
1)讀取sentinel相關配置
2)初始化sentinel狀態,並新增master、sentinel及slave到相應的字典
3)註冊sentinel相關的time event(週期性執行)
redis-sentinel和redis-server是同一個程式,檢視Makefile檔案可知:
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME)
main函式
int main(int argc, char **argv) { ...... //checkForSentinelMode判斷是否以sentinel模式啟動 //執行程式名為redis-sentinel,或者帶引數--sentinel執行則認為以sentinel模式執行 server.sentinel_mode = checkForSentinelMode(argc,argv); initServerConfig(); //sentinel模式下需要完成的初始化工作 if (server.sentinel_mode) { initSentinelConfig(); initSentinel(); } if (argc >= 2) { ...... //匯入配置 loadServerConfig(configfile,options); sdsfree(options); } ...... //註冊定時器 initServer(); ...... //判斷config檔案是否存在及是否可寫(sentinel模式需要寫config檔案) if (!server.sentinel_mode) { ...... } else { sentinelIsRunning(); } //以下開始進入事件處理迴圈 aeSetBeforeSleepProc(server.el,beforeSleep); aeMain(server.el); aeDeleteEventLoop(server.el); return 0; }
main函式中的loadServerConfig呼叫了loadServerConfigFromString函式,而loadServerConfigFromString在sentinel模式下會呼叫sentinelHandleConfiguration函式
此處先說明sentinel用到的幾個核心資料結構:
sentinelState為“頂級”資料結構,描述了當前執行的sentinel例項所包含的所有狀態,其master欄位指向包含了該sentinel結點監控的所有master(sentinelRedisInstance )例項狀態的字典
sentinelRedisInstance 描述了sentinel監控的“master”的狀態
struct sentinelState { uint64_t current_epoch; //當前處在第幾個世紀(每次fail over,current_epoch+1) dict *masters; /* master例項字典(一個sentinle可監控多個master)*/ int tilt; /*是否在TITL模式中,後面詳細介紹TITL模式*/ int running_scripts; /* 當前正在執行的指令碼 */ mstime_t tilt_start_time; /* TITL模式開始的時間 */ mstime_t previous_time; /* 上次執行sentinel週期性執行任務的時間,用以判斷是否進入TITL模式*/ list *scripts_queue; /* 待執行指令碼佇列 */ } sentinel; typedef struct sentinelRedisInstance { ...... /* Master specific. */ dict *sentinels; /* 監控該master例項的其他sentinel結點字典*/ dict *slaves; /* 該master例項說包含的slave結點字典 */ ...... } sentinelRedisInstance;
sentinelHandleConfiguration函式會handle sentinel相關的配置,建立master、slave、sentinel等例項,並根據配置初始化一些引數,包括髮現redis instance多長時間後判斷其為down
其中建立slave和sentinel的配置可以在sentinel執行過程中生成,也可以使用者配置,like:
sentinel known-slave mymaster 10.2.1.53 6379
sentinel known-sentinel mymaster 10.2.60.50 36379 33b297cfba3a7aece69fc999bb7150fa227e4fe7
char *sentinelHandleConfiguration(char **argv, int argc) {
sentinelRedisInstance *ri;
//Handle 類似“sentinel monitor mymaster 10.2.60.50 6379 2”的配置
//呼叫createSentinelRedisInstance建立master例項(SRI_MASTER)
if (!strcasecmp(argv[0],"monitor") && argc == 5) {
/* monitor <name> <host> <port> <quorum> */
int quorum = atoi(argv[4]);
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";
}
}
.....
//呼叫createSentinelRedisInstance建立slave例項(SRI_SLAVE)
} else if (!strcasecmp(argv[0],"known-slave") && argc == 4) {
sentinelRedisInstance *slave;
/* known-slave <name> <ip> <port> */
//根據master name獲取對應的master例項
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
if ((slave = createSentinelRedisInstance(NULL,SRI_SLAVE,argv[2],
atoi(argv[3]), ri->quorum, ri)) == NULL)
{
return "Wrong hostname or port for slave.";
}
//呼叫createSentinelRedisInstance建立sentinel例項(SRI_SENTINEL)
} else if (!strcasecmp(argv[0],"known-sentinel") &&
(argc == 4 || argc == 5)) {
sentinelRedisInstance *si;
// known-sentinel <name> <ip> <port> [runid]
//根據master name獲取對應的master例項
ri = sentinelGetMasterByName(argv[1]);
//對於slave和sentinel例項,定義其hostname為ip:port
if (flags & (SRI_SLAVE|SRI_SENTINEL)) {
snprintf(slavename,sizeof(slavename),
strchr(hostname,':') ? "[%s]:%d" : "%s:%d",
hostname,port);
name = slavename;
}
//根據例項型別不同,新增到不同的table
if (flags & SRI_MASTER) table = sentinel.masters;
else if (flags & SRI_SLAVE) table = master->slaves;
else if (flags & SRI_SENTINEL) table = master->sentinels;
sdsname = sdsnew(name);
if (dictFind(table,sdsname)) {
sdsfree(sdsname);
errno = EBUSY;
return NULL;
}
......
dictAdd(table, ri->name, ri);
return ri;
}
main函式呼叫的initSentinel函式初始化了sentinel能夠支援的客戶端命令:
void initSentinel(void) {
int j;
//空command字典
dictEmpty(server.commands,NULL);
//新增sentinal模式下支援的命令,sentinelcmds包括:ping、sentinel、subscribe、unsubscribe、psubscribe、info、shutdown
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);
redisAssert(retval == DICT_OK);
}
......
}
main函式呼叫的initServer會完成一些初始化工作,並註冊定時器
void initServer() {
......
//註冊定時器,定時時間1ms
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
redisPanic("Can't create the serverCron time event.");
exit(1);
}
......
}
serverCron服務啟動後1ms第一次執行,以後每隔100ms執行一次,serverCron會執行sentinel相關任務
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
......
/* 如果在sentinel模式下,則執行sentinel相關的週期性任務 */
run_with_period(100) { //100ms執行一次
if (server.sentinel_mode) sentinelTimer();
}
server.cronloops++;
return 1000/server.hz; //hz預設值為10(在sentinelTimer會被修改),此處返回100ms會被其它函式撲捉到,並重新註冊為定時函式
}
sentinelTimer內部包含sentinel模式需要定期執行的操作,包括check master、slave、sentinel的狀態,並根據配置的條件判斷是否需要fail over。