写点什么

一文带你探究 Sentinel 的独特初始化

发布于: 2021 年 01 月 15 日

摘要:本系列通过作者对 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 的初始化》,原文作者:中间件小哥 。


点击关注,第一时间了解华为云新鲜技术~


发布于: 2021 年 01 月 15 日阅读数: 22
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论 (3 条评论)

发布
用户头像
是和知乎等平台同步的吧,好多平台上都看到了。。~~
2021 年 01 月 16 日 14:40
回复
是哒,都是我们的官方账号
2021 年 01 月 18 日 09:07
回复
O(∩_∩)O哈哈~
2021 年 01 月 18 日 09:46
回复
没有更多了
一文带你探究Sentinel的独特初始化