Redis Sentinel实现(上)
1. Redis Sentinel 介绍和部署
sentinel.c
文件详细注释:Redis Sentinel详细注释
本文会分为两篇分别接受Redis Sentinel
的实现,本篇主要将Redis
哨兵的执行过程和执行的内容。
标题4
将会在Redis Sentinel实现(下)中详细剖析。
2. Redis Sentinel 的执行过程和初始化
Sentinel
本质上是一个运行在特殊模式下的Redis
服务器,无论如何,都是执行服务器的main
来启动。主函数中关于Sentinel
启动的代码如下:
int main(int argc, char **argv) {
// 1. 检查开启哨兵模式的两种方式
server.sentinel_mode = checkForSentinelMode(argc,argv);
// 2. 如果已开启哨兵模式,初始化哨兵的配置
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
// 3. 载入配置文件
loadServerConfig(configfile,options);
// 开启哨兵模式,哨兵模式和集群模式只能开启一种
if (!server.sentinel_mode) {
// 在不是哨兵模式下,会载入AOF文件和RDB文件,打印内存警告,集群模式载入数据等等操作。
} else {
sentinelIsRunning();
}
}
以上过程可以分为四步:
- 检查是否开启哨兵模式
- 初始化哨兵的配置
- 载入配置文件
- 开启哨兵模式
2.1 检查是否开启哨兵模式
在Redis Sentinel 介绍与部署文章中,介绍了两种开启的方法:
redis-sentinel sentinel.conf
redis-server sentinel.conf --sentinel
主函数中调用了checkForSentinelMode()
函数来判断是否开启哨兵模式。
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;
}
如果开启了哨兵模式,就会将server.sentinel_mode
设置为1
。
2.2 初始化哨兵的配置
在主函数中调用了两个函数initSentinelConfig()
和initSentinel()
,前者用来初始化Sentinel
节点的默认配置,后者用来初始化Sentinel
节点的状态。sentinel.c
文件详细注释:Redis Sentinel详细注释
在sentinel.c
文件中定义了一个全局变量sentinel
,它是struct sentinelState
类型的,用于保存当前Sentinel
的状态。
initSentinelConfig()
,初始化哨兵节点的默认端口为26379。
// 设置Sentinel的默认端口,覆盖服务器的默认属性
void initSentinelConfig(void) {
server.port = REDIS_SENTINEL_PORT;
}
initSentinel()
,初始化哨兵节点的状态
// 执行Sentinel模式的初始化操作
void initSentinel(void) {
unsigned int j;
/* Remove usual Redis commands from the command table, then just add
* the SENTINEL command. */
// 将服务器的命令表清空
dictEmpty(server.commands,NULL);
// 只添加Sentinel模式的相关命令,Sentinel模式下一共11个命令
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);
}
/* Initialize various data structures. */
// 初始化各种Sentinel状态的数据结构
// 当前纪元,用于实现故障转移操作
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();
// Sentinel通过流言协议接收关于主服务器的ip和port
sentinel.announce_ip = NULL;
sentinel.announce_port = 0;
// 故障模拟
sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
// Sentinel的ID置为0
memset(sentinel.myid,0,sizeof(sentinel.myid));
}
在哨兵模式下,只有11条命令可以使用,因此要用哨兵模式的命令表来代替Redis原来的命令表。
之后就是初始化sentinel
的成员变量。我们重点关注这几个成员:
- dict *masters :当前哨兵节点监控的主节点字典。字典的键是主节点实例的名字,字典的值是一个指针,指向一个
sentinelRedisInstance
类型的结构。 - int running_scripts: 当前正在执行的脚本的数量。
- list *scripts_queue:保存要执行用户脚本的队列。
2.3 载入配置文件
在启动哨兵节点时,要指定一个.conf
配置文件,配置文件可以将配置项分为两类。
- sentinel monitor \ \ \ \
- 例如:
sentinel monitor mymaster 127.0.0.1 6379 2
- 当前Sentinel节点监控 127.0.0.1:6379 这个主节点
- 2 代表判断主节点失败至少需要2个Sentinel节点节点同意
- mymaster 是主节点的别名
- 例如:
- sentinel xxxxxx \ xxxxxx
- 例如:
sentinel down-after-milliseconds mymaster 30000
- 每个Sentinel节点都要定期PING命令来判断Redis数据节点和其余Sentinel节点是否可达,如果超过30000毫秒且没有回复,则判定不可达。
- 例如:
sentinel parallel-syncs mymaster 1
- 当Sentinel节点集合对主节点故障判定达成一致时,Sentinel领导者节点会做故障转移操作,选出新的主节点,原来的从节点会向新的主节点发起复制操作,限制每次向新的主节点发起复制操作的从节点个数为1。
- 例如:
配置文件以这样的格式告诉哨兵节点,监控的主节点是谁,有什么样的限制条件。如果想要监控多个主节点,只需按照此格式在配置文件中多写几份。
既然配置文件都是如此,那么处理的函数也是如此处理,由于配置项很多,但是大体相似,所以我们列举处理示例的代码块:
sentinelRedisInstance *ri;
// SENTINEL monitor选项
if (!strcasecmp(argv[0],"monitor") && argc == 5) {
/* monitor <name> <host> <port> <quorum> */
int quorum = atoi(argv[4]); //获取投票数
// 投票数必须大于等于1
if (quorum <= 0) return "Quorum must be 1 or greater.";
// 创建一个主节点实例,并加入到Sentinel所监控的master字典中
if (createSentinelRedisInstance(argv[1],SRI_MASTER,argv[2],