Redis Sentinel 源码分析(1)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);  
  
        /* Translate the command string flags description into an actual 
         * set of flags. */  
        if (populateCommandTableParseFlags(cmd,cmd->sflags) == C_ERR)  
            serverPanic("Unsupported command flag");  
    }  
  
    /* Initialize various data structures. */  
    sentinel.current_epoch = 0;  
    sentinel.masters = dictCreate(&instancesDictType,NULL);  
    sentinel.tilt = 0;  
    sentinel.tilt_start_time = 0;  
    sentinel.previous_time = mstime();  
    .............  
}
1.使用Sentinel自带的命令表去替代Redis服务器原生的命令. 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},  
    {"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设计与实现第二版 黄健宏著
- 点赞
 - 收藏
 - 关注作者
 
            
           
评论(0)