上节我们看过了
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
模式启动的话,会执行
initSentinelConfig
和
initSentinel
函数,首先是
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;
}
}
}
…………………………………………………
继续看
sentinel
HandleConfiguration
函数
/* ============================ 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
的定时器周期的进行状态检查以及故障转移功能了。