redis集群实现(七)sentinel数据结构和初始

本文探讨了Redis Sentinel作为独立协调模块的角色,它不存储用户数据,专注于监控集群节点状态及故障转移。Sentinel是redis-server的一种特殊模式,通过配置文件启动并初始化,包括设置数据结构、配置解析、命令集定义以及连接集群节点,为后续的状态检查和故障转移做好准备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上节我们看过了sentinel协调者模块处理集群故障节点的功能,今天我们看一下sentinel的处理架构以及sentinel的初始化流程。

首先,sentinel是独立于数据节点之外的一个协调模块,sentinel不存储任何用户要求存储的key-value数据,sentinel只负责监视集群中每一个节点的运行状态以及处理故障转移。

首先sentinel其实就是redis进程的另一种模式,在Makefile里可以看出来,

# redis-server
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)                                                                                                    
    $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a $(FINAL_LIBS)

# redis-sentinel
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
    $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME)

首先把所有的object编译,然后把所有的中间文件链接成为$(REDIS_SERVER_NAME),就是redis-server,然后安装的时候直接把redis-server安装成为redis-sentinel

我们首先看看sentinel的数据结构

/* sentinel主要状态结构体. */                                        
 struct sentinelState {                                
     uint64_t current_epoch;     /* 当前的选举纪元,用于故障转移 */  
     dict *masters;      /* sentinel正在监视的master实例哈希表,key是实例名称,value是sentinelRedisInstance指针 */
     int tilt;           /* tilt模式 */                
     int running_scripts;    /* 当前正在执行的脚本数目. */    
     mstime_t tilt_start_time;   /* titl启动时间. */      
     mstime_t previous_time;     /* 上一次执行时间处理函数. */                
     list *scripts_queue;    /* 脚本执行队列. */        
     char *announce_ip;      /* 如果不是NULL,就代表通过gossip协议向此节点扩散. */   
     int announce_port;      /* 如果不为0,就会被其他节点扩散 */ 
 } sentinel;

然后我们再看看sentinelRedisInstance结构体,在sentinel中,每一个redis的主节点或者从节点,sentinel节点都会有一个此结构体表示

typedef struct sentinelRedisInstance {  
     int flags;      /* 当前实例的类型 */                         
     char *name;     /* 实例名称. */           
     char *runid;    /* 本实例的运行时ID. */                      
     uint64_t config_epoch;  /* 配置的初始选举纪元. */                          
     sentinelAddr *addr; /* 实例的地址 */                                                                  
 …………………………………………………                                         
     /* 针对监视的master节点独有的. */                                      
     dict *sentinels;    /* 其他正在监视这个master节点的sentinel. */                   
     dict *slaves;       /* 此master的slave. */           
     unsigned int quorum;/* 达成客观一致所需要的最小数目. */                            
…………………………………………………                       
     /* 针对监视的slave节点独有的. */                                     
…………………………………………………                              
     /* 故障转移处理涉及到的变量 */                                        
…………………………………………………                                                    
 } sentinelRedisInstance;

我们继续看sentinel初始化相关流程。

在启动sentinel的时候我们只需要redis-sentinel sentinel.conf就可以启动一个sentinel实例,配置文件中有redis集群中的master节点的IP,端口,名称以及处理故障转移的时间限制等参数。

main函数里边首先会检查是什么模式启动的,如果是sentinel模式启动的,就记录下来。

int main(int argc, char **argv) {
    struct timeval tv;
…………………………………………………    
    // 检查服务器是否以 Sentinel 模式启动       
    server.sentinel_mode = checkForSentinelMode(argc,argv);

    // 初始化服务器
    initServerConfig();

    /* 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. */
    // 如果服务器以 Sentinel 模式启动,那么进行 Sentinel 功能相关的初始化
    // 并为要监视的主服务器创建一些相应的数据结构
    if (server.sentinel_mode) {
        initSentinelConfig();            
        initSentinel();
…………………………………………………      
	//初始化配置文件
         if (configfile) server.configfile = getAbsolutePath(configfile);                           
         resetServerSaveParams();                            
         loadServerConfig(configfile,options);                            
         sdsfree(options);
…………………………………………………      
    }
…………………………………………………      

可以看出,如果是以sentinel模式启动的话,会执行initSentinelConfiginitSentinel函数,首先是initSentinelConfig函数初始化sentinel的配置,最后执行loadServerConfig函数来解析配置文件

void initSentinelConfig(void) {            
    server.port = REDIS_SENTINEL_PORT;
}

仅仅是配置下sentinel的专属端口,我们继续看下initSentinel函数。

// 在sentinel 模式下初始化
void initSentinel(void) {
    int j;

    // 清空服务器命令哈希表
    dictEmpty(server.commands,NULL);
    // 填充sentinel的专属命令
    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);
    }

    //初始化 Sentinel 的状态,比如选举纪元以及保存master信息的字典
    sentinel.current_epoch = 0;

    sentinel.masters = dictCreate(&instancesDictType,NULL);

    // 初始化tilt相关
    sentinel.tilt = 0;
    sentinel.tilt_start_time = 0;
    sentinel.previous_time = mstime();

    // 初始化运行脚本的变量
    sentinel.running_scripts = 0;
    sentinel.scripts_queue = listCreate();
} 

initSentinel函数初始化了命令集以及处理故障转移相关的变量,sentinel专属的命令集喝如下:

// 服务器在 sentinel 模式下可执行的命令
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},
    {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
};

最后看看loadServerConfig函数,

 void loadServerConfig(char *filename, char *options) {                                                                                                       
     sds config = sdsempty();                                                                                                                                 
     char buf[REDIS_CONFIGLINE_MAX+1];                                                                                                                        
                                                                                                                                                              
     /* 载入文件内容 */                                                                                                                              
     if (filename) {                                                                                                                                          
         FILE *fp;                                                                                                                                            
                                                                                                                                                              
         if (filename[0] == '-' && filename[1] == '\0') {                                                                                                     
             fp = stdin;                                                                                                                                      
         } else {                                                                                                                                             
             if ((fp = fopen(filename,"r")) == NULL) {                                                                                                                         redisLog(REDIS_WARNING,                                                                                                                      
                     "Fatal error, can't open config file '%s'", filename);                                                                                   
                 exit(1);                                                                                                                                     
             }                                                                                                                                                
         }                                                                                                                                                    
         while(fgets(buf,REDIS_CONFIGLINE_MAX+1,fp) != NULL)                                                                                                  
             config = sdscat(config,buf);                                                                                                                     
         if (fp != stdin) fclose(fp);                                                                                                                         
     }                                                                                                                                                        
     /* Append the additional options */                                                                                                                      
     if (options) {                                                                                                                                           
         config = sdscat(config,"\n");                                                                                                                        
         config = sdscat(config,options);                                                                                                                     
     }                                  
     //读取完毕,解析配置                                                                                                                      
     loadServerConfigFromString(config);                                                                                                                      
     sdsfree(config);                                                                                                                                         
 } 

然后我们进入loadServerConfigFromString函数,

void loadServerConfigFromString(char *config) {                           
     char *err = NULL;                             
     int linenum = 0, totlines, i;                             
     int slaveof_linenum = 0;                             
     sds *lines;  

     for (i = 0; i < totlines; i++) {      
…………………………………………………      
          } else if (!strcasecmp(argv[0],"sentinel")) {                            
             /* sentinel启动必须有一个config文件*/  
             if (argc != 1) {                                    
                 if (!server.sentinel_mode) {                             
                     err = "sentinel directive while not in sentinel mode";                                 
                     goto loaderr;                            
                 }                            
		//解析配置文件的主函数                
                 err = sentinelHandleConfiguration(argv+1,argc-1);                           
                 if (err) goto loaderr;                            
             }                              
         }
     }
…………………………………………………      

继续看sentinelHandleConfiguration函数

/* ============================ Config handling ============================= */                                                                             
 char *sentinelHandleConfiguration(char **argv, int argc) {                                                                                                   
     sentinelRedisInstance *ri;                                                                                                                               
     //根据配置文件,创建sentinel实例
     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";                                                                                                       
             }                                                                                                                                                
         }                                                                                  
     //解析其他的sentinel故障转移相关的参数         
…………………………………………………                                                                                                                                                                                                        
     } else {                                                                                                                                                 
         return "Unrecognized sentinel configuration statement.";                                                                                             
     }                                                                                                                                                        
     return NULL;                                                                                                                                             
 } 

自此,sentinel初始化完毕,然后第一个周期会和redis节点进行连接,接下来就等待sentinel的定时器周期的进行状态检查以及故障转移功能了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值