共计 4617 个字符,预计需要花费 12 分钟才能阅读完成。
摘要:本系列通过作者对 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 函数会监测以下两种条件:
- 程序应用 redis-sentinel 可执行文件执行。
- 程序参数列表中有 –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 函数,做以下几个工作:
- 查看配置文件是否被设置,并且检查程序对配置文件是否有写权限,因为如果 Sentinel 状态扭转的话,会一直将本人以后状态记录在配置文件中。
- 如果在配置文件中指定运行 ID,Sentinel 会应用这个 ID 作为运行 ID,相同地,如果没有指定运行 ID,Sentinel 会生成一个 ID 用来作为 Sentinel 的运行 ID。
- 对所有的 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 的初始化》,原文作者:中间件小哥。
点击关注,第一工夫理解华为云陈腐技术~