sentinel(哨兵)是Redis的高可用性解决方案:由一个或多个Sentinel实例( instance)组成的Sentinc1系统( system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
如果主服务器server1进入下线状态,那么从服务器server2、server3、server4对主服务器的复制操作将被中止,并且 Sentinel系统会察觉到server1 已下线。
当server1的下线时长超过用户设定的下线时长上限时,Sentinel系统就会对server1执行故障转移操作:
启动一个Sentinel可以使用命令:
redis-sentinel /path/tolyour/sentine1.conf
或
redis-server /path/tol your/sentinel.conf --sentinel
这两个命令的效果完全相同。当一个Sentinel启动时,它需要执行以下步骤:
(1) 初始化服务器
Sentinel本质上就是一个运行在特殊模式下的Redis服务器,因此就是初始化一个普通的Redis服务器。
不过,因为Sentinel执行的工作和普通Redis服务器执行的工作不同,所以Sentinel的初始化过程和普通Redis服务器的初始化过程并不完全相同。
例如,普通服务器在初始化时会通过载入RDB文件或者AOF文件来还原数据库状态,但是因为Sentinel并不使用数据库,所以初始化Sentinel时就不会载入RDB文件或者AOF文件。
(2) 判断sentinel模式
main 函数在调用 initServerConfig 函数初始化各种配置项之前,会调用 checkForSentinelMode 函数,来判断当前运行的是否为哨兵实例,如下所示:
//检验是否为sentinel模式
server.sentinel_mode = checkForSentinelMode(argc,argv);
//初始化服务器配置
initServerConfig();
checkForSentinelMode 函数(在 server.c 文件中)的参数,是 main 函数收到的启动命令字符串 argv 和启动命令中的参数个数 argc。然后,它会根据以下两个条件判断当前是否运行了哨兵实例。
/* Returns 1 if there is --sentinel among the arguments or if
* argv[0] contains "redis-sentinel". */
int checkForSentinelMode(int argc, char **argv) {
int j;
// 第一个判断条件,判断执行命令本身是否为redis-sentinel
if (strstr(argv[0],"redis-sentinel") != NULL) return 1;
for (j = 1; j < argc; j++)
// 第二个判断条件,判断命令参数是否有"--sentienl"
if (!strcmp(argv[j],"--sentinel")) return 1;
return 0;
}
其实,这两个判断条件也就对应了我们在命令行启动哨兵实例的两种方式,一种是直接运行 redis-sentinel 命令,另一种是运行 redis-server 命令,但是带有“–sentinel”参数。
所以,如果这两个条件中有一个成立,那么全局变量 server 的成员变量 sentinel_mode,就会被设置为 1,表明当前运行的是哨兵实例。这样一来,server.sentinel_mode 这一配置项就会在源码的其他地方,被用来判断当前是否运行的是哨兵实例。
(3) 使用Sentinel专用代码并初始化Sentinel状态
使用Sentinel专用代码即将普通Redis服务器使用的代码替换成Sentinel专用代码。比如:
初始化Sentinel状态就是初始化sentinelState构造体。
主要是通过sentinel.c中的两个函数initSentinelConfig和initSentinel来实现:
int main(int argc, char **argv) {
...
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
...
}
void initSentinelConfig(void) {
//#define REDIS_SENTINEL_PORT 26379
server.port = REDIS_SENTINEL_PORT;
server.protected_mode = 0; /* Sentinel must be exposed. */
}
/* 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);
}
/* 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();
sentinel.running_scripts = 0;
sentinel.scripts_queue = listCreate();
sentinel.announce_ip = NULL;
sentinel.announce_port = 0;
sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG;
memset(sentinel.myid,0,sizeof(sentinel.myid));
}
initSentinelConfig 函数主要是将当前 server 的端口号,改为哨兵实例专用的端口号 REDIS_SENTINEL_PORT。这是个宏定义,它对应的默认值是 26379。另外,这个函数还会把 server 的 protected_mode 设置为 0,即允许外部连接哨兵实例,而不是只能通过 127.0.0.1 本地连接 server;
initSentinel 函数是在 initSentinelConfig 函数的基础上,进一步完成哨兵实例的初始化,这其中主要包括两部分工作:
1)替换 server 能执行的命令表,普通Redis服务器使用redis.c/redisCommandTable作为服务器的命令表,而Sentinel则使用sentinel.c/sentinelcmds作为服务器的命令表:
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,"l",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0}
};
sentinelcmds命令表也解释了为什么在Sentinel模式下,Redis服务器不能执行诸如SET、DBSIZE、EVAL等等这些命令,因为服务器根本没有在命令表中载入这些命令。
其中你需要注意的是,哨兵实例执行的一些命令,其名称虽然和 Redis 实例命令表中的命令名称一样,但它们的实现函数是针对哨兵实例专门实现的。比如,哨兵实例和 Redis 实例都可以执行 publish、info、role 命令,但是在哨兵实例中,这三个命令分别由 sentinelPublishCommand、sentinelInfoCommand、sentinelRoleCommand 这三个在 sentinel.c 文件中的函数来实现的。所以,当你需要详细了解哨兵实例运行命令的实现时,注意不要找错代码文件。
2)其次,initSentinel 函数在替换了命令表后,紧接着它会开始初始化哨兵实例用到的各种属性信息。为了保存这些属性信息,哨兵实例定义了 sentinelState 结构体(在 sentinel.c 文件中),这其中包括了哨兵实例的 ID、用于故障切换的当前纪元、监听的主节点、正在执行的脚本数量,以及与其他哨兵实例发送的 IP 和端口号等信息。
struct sentinelState {
char myid[CONFIG_RUN_ID_SIZE+1]; //哨兵实例ID
uint64_t current_epoch; //当前纪元
dict *masters; //保存当前监听的主节点实例,key为实例id,value为实例指针,指向一个sentinelRedisInstance
int tilt; //是否处于TILT模式
int running_scripts; //运行的脚本个数
mstime_t tilt_start_time; //tilt模式的起始时间
mstime_t previous_time; //上一次执行时间处理函数的时间
list *scripts_queue; //用于保存脚本的队列
char *announce_ip; //向其他哨兵实例发送的IP信息
int announce_port; //向其他哨兵实例发送的端口号
unsigned long simfailure_flags; /* Failures simulation. */
int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script
paths at runtime? */
} sentinel;
(4) 根据给定的配置文件,初始化Sentinel的监视主服务器列表
redis的main函数中,会调用 loadServerConfig 加载配置文件,loadServerConfig 函数会读取配置文件中的内容,然后调用 loadServerConfigFromString 函数解析具体的配置项。
loadServerConfigFromString 函数中,其中有一个分支,对哨兵模式进行了判断,如果是哨兵模式,则调用 sentinelHandleConfiguration 函数解析哨兵配置项。
所以,函数调用链为 main -> loadServerConfig(读出配置文件内容) -> loadServerConfigFromString(解析配置项) -> sentinelHandleConfiguration(解析哨兵配置项)。
简单看下sentinel.c#sentinelHandleConfiguration函数的两个解析部分:
//创建监听的master实例
if (!strcasecmp(argv[0],"monitor") && argc == 5) {
/* monitor */
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";
}
}
}
...
//创建已知的监听同一个master的sentinel实例
else if (!strcasecmp(argv[0],"known-sentinel") &&
(argc == 4 || argc == 5)) {
sentinelRedisInstance *si;
if (argc == 5) { /* Ignore the old form without runid. */
/* known-sentinel [runid] */
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
if ((si = createSentinelRedisInstance(argv[4],SRI_SENTINEL,argv[2],
atoi(argv[3]), ri->quorum, ri)) == NULL)
{
return "Wrong hostname or port for sentinel.";
}
si->runid = sdsnew(argv[4]);
sentinelTryConnectionSharing(si);
}
}
(5) 启动哨兵实例
在InitServerLast()后会调用sentinelsRunning来启动哨兵实例:
InitServerLast();
sentinelIsRunning();
主要是做了三件事情:
/* This function gets called when the server is in Sentinel mode, started,
* loaded the configuration, and is ready for normal operations. */
// 当服务器处于 Sentinel 模式、启动、加载配置并准备好进行正常操作时,将调用此函数。
void sentinelIsRunning(void) {
int j;
/* 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. */
// 如果这个 Sentinel 还没有在配置文件中设置 ID,我们随机选择一个并将配置保存在磁盘上。从现在开始,这将是重启后的 Sentinel ID。
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. */
// 生成一个16进制的随机ID,选择 ID 并持久化配置
getRandomHexChars(sentinel.myid,CONFIG_RUN_ID_SIZE);
sentinelFlushConfig();
}
/* Log its ID to make debugging of issues simpler. */
// 记录其 ID 以简化问题的调试。
serverLog(LL_WARNING,"Sentinel ID is %s", sentinel.myid);
/* We want to generate a +monitor event for every configured master
* at startup. */
// 我们希望在启动时为每个配置的 master 生成一个 +monitor 事件。
sentinelGenerateInitialMonitorEvents();
}
sentinelIsRunning 函数是如何获取到主节点的地址信息呢?
这就和刚才介绍的 initSentinel 函数有关了,它会初始化哨兵实例的数据结构 sentinel.masters。这个结构是使用了一个哈希表记录监听的主节点,每个主节点会使用 sentinelRedisInstance 结构来保存。而在 sentinelRedisInstance 结构中,就包含了被监听主节点的地址信息。这个地址信息是由 sentienlAddr 结构体保存的,其中包括了节点的 IP 和端口号。
typedef struct sentinelRedisInstance {
int flags; //实例类型、状态的标记,它可以设置为不同的值,从而表示不同类型的实例。比如,当 flags 设置为 SRI_MASTER、SRI_SLAVE 或 SRI_SENTINEL 这三种宏定义(在 sentinel.c 文件中)时,就分别表示当前实例是主节点、从节点或其他sentinel。SRI_MASTER_DOWN表示其它sentinel认为其主节点下线,SRI_S_DOWN当前sentinel认为主节点主观下线,SRI_O_DOWN当前sentinel认为主节点主客观下线
char *name; //实例名称
char *runid; //实例ID
uint64_t config_epoch; //配置的纪元
sentinelAddr *addr; //实例地址信息
...
mstime_t s_down_since_time; //主观下线的时长
mstime_t o_down_since_time; //客观下线的时长
...
/*
* 主节点特有的
*/
dict *sentinels; //监听同一个主节点的其他哨兵实例
dict *slaves; //主节点的从节点
unsigned int quorum; //sentinel leader选举时的阈值
int parallel_syncs; //当进行故障转移时,同时只允许parallel_syncs个从节点正在进行同步操作
...
}
typedef struct sentinelAddr {
char *ip;
int port;
} sentinelAddr;
这个 sentinelGenerateInitialMonitorEvents 函数的执行逻辑:
// 此函数仅在启动时调用,用于为每个配置的主站生成 +monitor 事件。
// 当通过 SENTINEL MONITOR 命令在运行时添加要监控的主设备时,也会生成相同的事件
void sentinelGenerateInitialMonitorEvents(void) {
dictIterator *di;
dictEntry *de;
// 获取masters的迭代器
di = dictGetIterator(sentinel.masters);
// 获取被监听的主节点
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
//发送消息
sentinelEvent(LL_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
}
dictReleaseIterator(di);
}
从代码中,你可以看到,sentinelGenerateInitialMonitorEvents 函数是调用 sentinelEvent 函数(在 sentinel.c 文件中)来实际发送事件信息的。
简单点来说:如果看到 sentinelEvent,这就表明哨兵正在用它来发布消息。具体可以看后面的源码分析部分。
(6) 创建网络连接
对于每个被Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接:
Q:为什么要两个连接?
A:在Redis目前的发布与订阅功能中,被发送的信息都不会保存在Redis服务器里面,如果在信息发送时,想要接收信息的客户端不在线或者断线,那么这个客户端就会丢失这条信息。因此,为了不丢失_sentinel_ :hello频道的任何信息,Sentinel必须专门用一个订阅连接来接收该频道的信息。
另一方面,除了订阅频道之外,Sentinel还必须向主服务器发送命令,以此来与主服务器进行通信,所以Sentinel还必须向主服务器创建命令连接。
因为Sentinel需要与多个实例创建多个网络连接,所以Sentinel使用的是异步连接。
Q:如何创建的?
A:在serverCron中每次会调用sentineltimer,如果链接没有建立,就会进行创建。
Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送 INFO命令,并通过分析INFO命令的回复来获取主服务器的当前信息。
# server
...
run_id:7611c59dc3a29aa6fa0609f841bb6a1019008a9c
...
# Replication
role:master
...
s1ave0:ip=127.0.0.1,port=11111,state=online,offset=43,lag=0
slave1:ip=127.0.0.1,port=22222,state=online,offset=43,lag-0
slave2:ip=127.0.0.1,port=33333,state=online,offset=43,lag-0
...
# other sections
...
主要包含以下信息:
参数名 | 说明 |
---|---|
server | 获取 server 信息 |
clients | 获取 clients 信息,如客户端连接数等 |
memory | 获取 server 的内存信息,包括当前内存消耗、内存使用峰值 |
persistence | 获取 server 的持久化配置信息 |
stats | 获取 server 的一些基本统计信息,如处理过的连接数量等 |
replication | 获取 server 的主从配置信息 |
cpu | 获取 server 的 CPU 使用信息 |
keyspace | 获取 server 中各个 DB 的 key 的数量 |
cluster | 获取集群节点信息,仅在开启集群后可见 |
commandstas | 获取每种命令的统计信息 |
通过分析主服务器返回的INFO命令回复,Sentinel可以获取以下两方面的信息:
根据run_id域和role域记录的信息,Sentinel将对主服务器的实例结构进行更新。至于主服务器返回的从服务器信息,则会被用于更新主服务器实例结构的slaves字典,这个字典记录了主服务器属下从服务器的名单:
当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实例结构之外,Sentinel还会创建连接到从服务器的命令连接和订阅连接。
在创建命令连接之后,Sentinel在默认情况下,会以每十秒一次的频率通过命令连接向从服务器发送INFO命令,并获得类似于以下内容的回复:
# Server
...
run_id:32be0699dd27b410f7c90dada3a6fab17f97899f
...
# Replication
role:slave
master_ host:127.0.o.1master_port : 6379
master_link_status : upslave_repl__offset: 11887slave_priority:10o
other sections
根据INFO命令的回复,Sentinel会提取出以下信息:
根据这些信息,Sentinel 会对从服务器的实例结构进行更新。
在默认情况下,Sentinel会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:
PUBLISH_sentinel__:he11o ",,,,,,,"
这条命令向服务器的_sentinel_:hello频道发送了一条信息,信息的内容由多个参数组成:
其中以s_开头的参数记录的是Sentinel本身的信息,而m_开头的参数记录的则是主服务器的信息。
我在看源码的过程中,发现了其实在sentinelTimer的sentinelSendPeriodicCommand中,其实会对已知的所有节点(主、从、sentinel)发布该PUBLISH命令,主、从节点会进入hello频道,而sentinel节点会调用其特定的命令处理函数sentinelPublishCommand来进行处理,且该函数仅仅会处理频道为hello的PUBLISH命令,也是调用sentinelProcessHelloMessage函数进行具体处理,和从hello频道中接收到消息的回调处理函数相同。
每个与Sentinel连接的服务器,Sentinel既通过命令连接向服务器的_sentinel_:hello频道发送信息,又通过订阅连接从服务器的_sentinel_ :hello频道接收信息。
对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他Sentinel 接收到,这些信息会被用于更新其他Sentinel对发送信息Sentinel的认知,也会被用于更新其他Sentinel对被监视服务器的认知。
当一个Sentinel从_sentinel_:hello频道收到一条信息时,Sentinel 会对这条信息进行分析,提取出信息中的Sentinel IP地址、Sentinel 端口号、Sentinel运行ID等八个参数,并进行以下检查:
Sentinel为主服务器创建的实例结构中的sentinels字典保存了除Sentinel本身之外,所有同样监视这个主服务器的其他Sentinel的资料:sentinels字典的键是sentinel的名字(ip:port),值是sentinel的实例。
当一个Sentinel接收到其他Sentinel发来的信息时,目标Sentinel会从信息中分析并提取出以下两方面参数:
根据信息中提取出的主服务器参数,目标Sentinel会在自己的Sentinel状态的masters字典中查找相应的主服务器实例结构,然后根据提取出的Sentinel参数,检查主服务器实例结构的sentinels字典中,源Sentinel的实例结构是否存在:
当Sentinel通过频道信息发现一个新的Sentinel时,它不仅会为新Sentinel在sentinels字典中创建相应的实例结构,还会创建一个连向新Sentinel的命令连接,而新Sentinel也同样会创建连向这个Sentinel的命令连接,最终监视同一主服务器的多个Sentinel将形成相互连接的网络:Sentinel A有连向Sentinel B的命令连接,而Sentinel B也有连向Sentinel A 的命令连接。
Sentinel 之间不会创建订阅连接。Sentinel在连接主服务器或者从服务器时,会同时创建命令连接和订阅连接,但是在连接其他Sentinel时,却只会创建命令连接,而不创建订阅连接。这是因为Sentinel需要通过接收主服务器或者从服务器发来的频道信息来发现未知的新Sentinel,所以才需要建立订阅连接,而相互已知的Sentinel只要使用命令连接来进行通信就足够了。
分为检测主观下线状态和检测客观下线状态。
检测主观下线状态:
在默认情况下,Sentinel 会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他Sentinel在内)发送PING命令,并通过实例返回的PING命令回复判断实例是否在线。
实例对PING命令的回复可以分为以下两种情况:
Sentinel 配置文件中的down-after-milliseconds选项指定了Sentinel判断实例进入主观下线所需的时间长度:如果一个实例在down-after-milliseconds毫秒内,连续向Sentinel返回无效回复,那么Sentinel会修改这个实例所对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,以此来表示这个实例已经进入主观下线状态。
检测客观下线状态:
当Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态(可以是主观下线或者客观下线)。当Sentinel 从其他Sentinel那里接收到足够数量的已下线判断之后,Sentinel就会将从服务器判定为客观下线,并对主服务器执行故障转移操作。
发送 SENTINEL is-master-down-by-addr命令
SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
ip:被Sentinel 判断为主观下线的主服务器的P地址
port:被Sentinel判断为主观下线的主服务器的端口号
current_epoch:Sentinel当前的配置纪元,用于选举领头 Sentinel
runid:可以是*符号或者Sentinel的运行ID:*符号代表命令仅仅用于检测主服务器的客观下线状态,而Sentinel的运行ID则用于选举领头 Sentinel。
接收SENTINEL is-master-down-by-addr命令
当一个 Sentinel(目标Sentinel)接收到另一个Sentinel(源Sentinel)发来的SENTINE工 is-master-down-by命令时,目标Sentinel 会分析并取出命令请求中包含的各个参数,并根据其中的主服务器IP和端口号,检查主服务器是否已下线,然后向源Sentinel返回一条包含三个参数的Multi Bulk 回复作为SENTINEL is-master-down-by命令的回复:
1 <down_state> //返回目标Sentincl对主服务器的检查结果,1代表主服务器已下线,0代表主服务器未下线
2 <leader_runid> //可以是*符号或者目标Sentincl的局部领头 Sentinel的运行ID:*符号代表命令仅仅用于检测主服务器的下线状态,而局部领头 Sentinel的运行ID则用于选举领头Sentinel
3 <leader_epoch> //目标Sentinel的局部领头Sentinel的配置纪元,用于选举领头Sentinel。仅在leader_runid的值不为*时有效,如果1eader_runid的值为*,那么leader_epoch总为0
接收SENTINEL is-master-down-by-addr命令的回复
根据其他Sentinel发回的SENTINEL is-master-down-by-addr命令回复,Sentinel将统计其他Sentinel同意主服务器已下线的数量,当这一数量达到配置指定的判断客观下线所需的数量时(quorum),Sentinel 会将主服务器实例结构flags属性的SRI_O_DOWN标识打开,表示主服务器已经进入客观下线状态。
当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentine1对下线主服务器执行故障转移操作。
以下是Redis选举领头 Sentinel 的规则和方法:
其实就是基本的leader半数选举规则。
一个案例过程:
假设现在有三个Sentinel正在监视同一个主服务器,并且这三个Sentinel之前已经通过SENTINEL is-master-down-by-addr命令确认主服务器进入了客观下线状态,如图:
那么为了选出领头Sentinel,三个Sentinel将再次向其他Sentine1发送SENTINEL is-master-down-by-addr命令,和检测客观下线状态时发送的SENTINEL is-master-down-by-addr命令不同,Sentinel这次发送的命令会带有Sentinel自己的运行ID,如
SENTINEL is-master-down-by-addr 127.o.o.1 6379 o e955b4c85598ef5b5f055bc7ebfd5e828dbed4fa
如果接收到这个命令的Sentinel还没有设置局部领头Sentinel的话,它就会将运行ID为e955b4c85598ef5b5f055bc7ebfd5e828dbed4fa的Sentinel设置为自己的局部领头Sentinel,并返回类似以下的命令回复:
1) 1
2) e955b4c85598ef5b5f055bc7ebfd5e828dbed4fa
3) 0
然后接收到命令回复的Sentinel就可以根据这一回复,统计出有多少个Sentinel将自己设置成了局部领头 Sentinel。然后局部领头Sentinel就可以开始对主服务器执行故障转移操作了。
在选举产生出领头 Sentinel之后,领头Sentinel将对已下线的主服务器执行故障转移操作,该操作包含以下三个步骤:
故障转移操作第一步要做的就是在已下线主服务器属下的所有从服务器中,挑选出一个状态良好、数据完整的从服务器,然后向这个从服务器发送SLAVEOF no one命令,将这个从服务器转换为主服务器。
新的主服务器是怎样挑选出来的?
领头 Sentinel会将已下线主服务器的所有从服务器保存到一个列表里面,然后按照以下规则,一项一项地对列表进行过滤:
当新的主服务器出现之后,领头Sentinel下一步要做的就是,让已下线主服务器属下的所有从服务器去复制新的主服务器,这一动作可以通过向从服务器发送SLAVEOF命令来实现。
故障转移操作最后要做的是,将已下线的主服务器设置为新的主服务器的从服务器。因为旧的主服务器已经下线,所以这种设置是保存在server1对应的实例结构里面的,当server1重新上线时,Sentinel就会向它发送SLAVEOF命令,让它成为server2的从服务器。