一文帶你探究Sentinel的獨特初始化
摘要:本系列通過作者對Redis Sentinel原始碼的理解,詳細說明Sentinel的程式碼實現方式。
Redis Sentinel 是Redis提供的高可用模型解決方案。Sentinel可以自動監測一個或多個Redis主備例項,並在主例項宕機的情況下自動實行主備倒換。本系列通過作者對Redis Sentinel原始碼的理解,詳細說明Sentinel的程式碼實現方式。
Sentinel使用Redis核心相同的事件驅動程式碼框架, 但Sentinel有自己獨特的初始化步驟。在這篇文章裡,作者會介紹Sentinel與Redis伺服器不同的初始化部分。
我們可以通過redis-sentinel <path-to-configfile> 或者 redis-server <path-to-configfile> --sentinel 這兩種方式啟動並執行Sentinel例項,這兩種方式是等價的。在Redis server.c 的main函式中,我們會看到Redis如何判斷使用者指定以Sentinel方式執行的邏輯:
int main(int argc, char **argv) { .......... server.sentinel_mode = checkForSentinelMode(argc,argv); .......... }
其中checkForSentinelMode函式會監測以下兩種條件:
1. 程式使用redis-sentinel可執行檔案執行。
2. 程式引數列表中有--sentinel 標誌。
以上任何一種條件成立則Redis會使用Sentinel的方式執行。
/* Returns 1 if there is --sentinel among the arguments or if * argv[0] contains "redis-sentinel". */ 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; }
在Redis 判斷是否以Sentinel的方式執行以後,我們會看到如下程式碼段:
int main(int argc, char **argv) {
struct timeval tv;
int j;
............
/* We need to init sentinel right now as parsing the configuration file
* in sentinel mode will have the effect of populating the sentinel
* data structures with master nodes to monitor. */
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
............
在initSentinelConfig函式中,會使用Sentinel特定的埠(預設為26379)來替代Redis的預設埠(6379)。另外,在Sentinel模式下,需要禁用伺服器執行保護模式。
/* This function overwrites a few normal Redis config default with Sentinel
* specific defaults. */
void initSentinelConfig(void) {
server.port = REDIS_SENTINEL_PORT;
server.protected_mode = 0; /* Sentinel must be exposed. */
}
與此同時,initSentinel函式會做如下操作:
/* Perform the Sentinel mode initialization. */
void initSentinel(void) {
unsigned int j;
/* Remove usual Redis commands from the command table, then just add
* the SENTINEL command. */
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);
{"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,"ok-loading",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"read-only no-script",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"no-auth no-script ok-loading ok-stale fast",0,NULL,0,0,0,0,0},
{"hello",helloCommand,-2,"no-auth no-script fast",0,NULL,0,0,0,0,0}
};
2.初始化Sentinel主狀態結構,Sentinel主狀態的定義及註釋如下。
/* Main state. */
struct sentinelState {
char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. */
uint64_t current_epoch; /* Current epoch. */
dict *masters; /* Dictionary of master sentinelRedisInstances.
Key is the instance name, value is the
sentinelRedisInstance structure pointer. */
int tilt; /* Are we in TILT mode? */
int running_scripts; /* Number of scripts in execution right now. */
mstime_t tilt_start_time; /* When TITL started. */
mstime_t previous_time; /* Last time we ran the time handler. */
list *scripts_queue; /* Queue of user scripts to execute. */
char *announce_ip; /* IP addr that is gossiped to other sentinels if
not NULL. */
int announce_port; /* Port that is gossiped to other sentinels if
non zero. */
unsigned long simfailure_flags; /* Failures simulation. */
int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script
paths at runtime? */
} sentinel;
其中masters字典指標中的每個值都對應著一個Sentinel檢測的主例項。
在讀取配置資訊後,Redis伺服器主函式會呼叫sentinelIsRunning函式, 做以下幾個工作:
1. 檢查配置檔案是否被設定,並且檢查程式對配置檔案是否有寫許可權,因為如果Sentinel狀態改變的話,會不斷將自己當前狀態記錄在配置檔案中。
2. 如果在配置檔案中指定執行ID,Sentinel 會使用這個ID作為執行ID,相反地,如果沒有指定執行ID,Sentinel會生成一個ID用來作為Sentinel的執行ID。
3. 對所有的Sentinel監測例項產生初始監測事件。
/* This function gets called when the server is in Sentinel mode, started,
* loaded the configuration, and is ready for normal operations. */
void sentinelIsRunning(void) {
int j;
if (server.configfile == NULL) {
serverLog(LL_WARNING,
"Sentinel started without a config file. Exiting...");
exit(1);
} else if (access(server.configfile,W_OK) == -1) {
serverLog(LL_WARNING,
"Sentinel config file %s is not writable: %s. Exiting...",
server.configfile,strerror(errno));
exit(1);
}
/* If this Sentinel has yet no ID set in the configuration file, we
* pick a random one and persist the config on disk. From now on this
* will be this Sentinel ID across restarts. */
for (j = 0; j < CONFIG_RUN_ID_SIZE; j++)
if (sentinel.myid[j] != 0) break;
if (j == CONFIG_RUN_ID_SIZE) {
/* Pick ID and persist the config. */
getRandomHexChars(sentinel.myid,CONFIG_RUN_ID_SIZE);
sentinelFlushConfig();
}
/* Log its ID to make debugging of issues simpler. */
serverLog(LL_WARNING,"Sentinel ID is %s", sentinel.myid);
/* We want to generate a +monitor event for every configured master
* at startup. */
sentinelGenerateInitialMonitorEvents();
}
參考資料:
https://github.com/antirez/redis
https://redis.io/topics/sentinel
Redis設計與實現第二版 黃健巨集著
本文分享自華為雲社群《Redis Sentinel 原始碼分析(1)Sentinel的初始化》,原文作者:中介軟體小哥 。