一 序
redis主从复制可将主节点数据同步给从节点,一旦主节点宕机,从节点作为主节点的备份可以随时顶上来。这个过程如果人工介入,效果肯定没有自动的高可用机制好。sentinel 哨兵机制就是为了解决这个问题。是redis的高可用HA方案:有一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
书上还花了图,这里略过。整个目录内容还是比较多的。所以本篇主要以书上内容为主,后面在看源码,4000多行,一篇估计也不够,得看几天了。
二 启动并初始化sentinel
启动一个 Sentinel 可以使用命令:
- $ redis-sentinel /path/to/your/sentinel.conf
- $ redis-server /path/to/your/sentinel.conf --sentinel
这两个命令都能启动Sentinel,效果都是一样的。
Sentinel启动后,会有五个步骤:
- 初始化服务器。
- 将普通 Redis 服务器使用的代码替换成 Sentinel 专用代码。
- 初始化 Sentinel 状态。
- 根据给定的配置文件, 初始化 Sentinel 的监视主服务器列表。
- 创建连向主服务器的网络连接。
下面分别展开。
2.1 初始化服务器
Sentinel的本质是一个运行在特殊模式下的Redis服务器,因此启动时必须对其进行初始化,但是由于Sentinel与普通的服务器不同,它的初始化需要执行的操作也不同。
表 TABLE_SENTINEL_FUNCTION Sentinel 模式下 Redis 服务器主要功能的使用情况
功能 | 使用情况 |
---|---|
数据库和键值对方面的命令, 比如 SET 、 DEL 、 FLUSHDB 。 | 不使用。 |
事务命令, 比如 MULTI 和 WATCH 。 | 不使用。 |
脚本命令,比如 EVAL 。 | 不使用。 |
RDB 持久化命令, 比如 SAVE 和 BGSAVE 。 | 不使用。 |
AOF 持久化命令, 比如 BGREWRITEAOF 。 | 不使用。 |
复制命令,比如 SLAVEOF 。 | Sentinel 内部可以使用,但客户端不可以使用。 |
发布与订阅命令, 比如 PUBLISH 和 SUBSCRIBE 。 | SUBSCRIBE 、 PSUBSCRIBE 、 UNSUBSCRIBE PUNSUBSCRIBE 四个命令在 Sentinel 内部和客户端都可以使用, 但 PUBLISH 命令只能在 Sentinel 内部使用。 |
文件事件处理器(负责发送命令请求、处理命令回复)。 | Sentinel 内部使用, 但关联的文件事件处理器和普通 Redis 服务器不同。 |
时间事件处理器(负责执行 serverCron 函数)。 | Sentinel 内部使用, 时间事件的处理器仍然是 serverCron 函数, serverCron 函数会调用 sentinel.c/sentinelTimer 函数, 后者包含了 Sentinel 要执行的所有操作。 |
2.2 使用Sentinel专用代码
启动Sentinel的第二步,就是将普通Redis服务器使用的代码替换成Sentinel专用的代码
比如 普通Redis服务器使用 redis.h/REDIS_SERVERPORT常量作为服务端口(#define REDIS_SERVERPORT 6379),使用 redis.h/redisCommandTable 作为服务器的命令表。
而Sentinel使用 reids.h/REDIS_SENTINEL_PORT 常量作为服务器端口,默认26379,使用 redis.h/sentinelcmds 作为服务器的命令表且其中的INFO 命令使用Sentinel模式下的专用实现 sentinel.c/sentinelInfoCommand 函数。
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}
};
sentinelcmds
命令表也解释了为什么在 Sentinel 模式下, Redis 服务器不能执行诸如 SET 、 DBSIZE 、 EVAL 等等这些命令 —— 因为服务器根本没有在命令表中载入这些命令。
2.3. 初始化Sentinel状态
服务器会初始化一个 sentinel.c/sentinelState
结构(简称“Sentinel状态”),这个结构保存了服务器所有和Sentinel功能有关的状态,服务器的一般状态仍然有 redis.h/redisServer
结构保存:
/* Main state. */
struct sentinelState {
// Sentinel的id,41字节长的字符串
char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. */
// 当前纪元,用于故障转移
uint64_t current_epoch; /* Current epoch. */
// 监控的主节点字典
// 键是主节点实例的名字
// 值是主节点一个指向 sentinelRedisInstance 结构的指针
dict *masters; /* Dictionary of master sentinelRedisInstances.
Key is the instance name, value is the
sentinelRedisInstance structure pointer. */
// 是否在TILT模式,该模式只收集数据,不做故障切换fail-over
int tilt; /* Are we in TILT mode? */
// 当前正在执行的脚本的数量
int running_scripts; /* Number of scripts in execution right now. */
// TILT模式开始的时间
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. */
// 多个 Sentinel 进程(progress)之间使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息,
// 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
// 被流言(gossip)到其他Sentinel的ip地址
char *announce_ip; /* IP addr that is gossiped to other sentinels if
not NULL. */
// 被流言(gossip)到其他Sentinel的port
int announce_port; /* Port that is gossiped to other sentinels if
non zero. */
// 故障模拟
unsigned long simfailure_flags; /* Failures simulation. */
} sentinel;
2.4 初始化Sentinel状态的 masters
属性
接下来要做的是将sentinel状态的 masters
属性进行初始化,上面已经说过了,masters
里面保存的是所有被监视的主服务器的信息以及其键值对应保存的是什么内容。
Sentinel 状态中的 masters
字典记录了所有被 Sentinel 监视的主服务器的相关信息, 其中:
- 字典的键是被监视主服务器的名字。
- 而字典的值则是被监视主服务器对应的
sentinel.c/sentinelRedisInstance
结构。
每个 sentinelRedisInstance
结构(后面简称“实例结构”)代表一个被 Sentinel 监视的 Redis 服务器实例(instance), 这个实例可以是主服务器、从服务器、或者另外一个 Sentinel 。
typedef struct sentinelRedisInstance {
// 标识值,记录了当前Redis实例的类型和状态
int flags; /* See SRI_... defines */
// 实例的名字
// 主节点的名字由用户在配置文件中设置
// 从节点以及Sentinel节点的名字由Sentinel自动设置,格式为:ip:port
char *name; /* Master name from the point of view of this sentinel. */
//实例的运行 ID
char *runid; /* Run ID of this instance, or unique ID if is a Sentinel.*/
//配置纪元,用于实现故障转移
uint64_t config_epoch; /* Configuration epoch. */
//实例的地址:ip和port
sentinelAddr *addr; /* Master host. */
//实例的连接,有可能是被Sentinel共享的
instanceLink *link; /* Link to the instance, may be shared for Sentinels. */
// 最近一次通过 Pub/Sub 发送信息的时间
mstime_t last_pub_time; /* Last time we sent hello via Pub/Sub. */
// 只有被Sentinel实例使用
// 最近一次接收到从Sentinel发送来hello的时间
mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time
we received a hello from this Sentinel
via Pub/Sub. */
// 最近一次回复SENTINEL is-master-down的时间
mstime_t last_master_down_reply_time; /* Time of last reply to
SENTINEL is-master-down command. */
// 实例被判断为主观下线的时间
mstime_t s_down_since_time; /* Subjectively down since time. */
// 实例被判断为客观下线的时间
mstime_t o_down_since_time; /* Objectively down since time. */
// 实例无响应多少毫秒之后才会被判断为主观下线(subjectively down)
mstime_t down_after_period; /* Consider it down after that period. */
// 从实例获取INFO命令回复的时间
mstime_t info_refresh; /* Time at which we received INFO output from it. */
/* Role and the first time we observed it.
* This is useful in order to delay replacing what the instance reports
* with our own configuration. We need to always wait some time in order
* to give a chance to the leader to report the new configuration before
* we do silly things. */
// 实例的角色
int role_reported;
// 角色更新的时间
mstime_t role_reported_time;
// 最近一次从节点的主节点地址变更的时间
mstime_t slave_conf_change_time; /* Last time slave master addr changed. */
/* Master specific. */
/*----------------------------------主节点特有的属性----------------------------------*/
// 其他监控相同主节点的Sentinel
dict *sentinels; /* Other sentinels monitoring the same master. */
// 如果当前实例是主节点,那么slaves保存着该主节点的所有从节点实例
// 键是从节点命令,值是从节点服务器对应的sentinelRedisInstance
dict *slaves; /* Slaves for this master instance. */
// 判定该主节点客观下线(objectively down)的投票数
// 由SENTINEL monitor <master-name> <ip> <port> <quorum>配置
unsigned int quorum;/* Number of sentinels that need to agree on failure. */
// SENTINEL parallel-syncs <master-name> <number> 选项的值
// 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
int parallel_syncs; /* How many slaves to reconfigure at same time. */
// 连接主节点和从节点的认证密码
char *auth_pass; /* Password to use for AUTH against master & slaves. */
/* Slave specific. */
/*----------------------------------从节点特有的属性----------------------------------*/
// 从节点复制操作断开时间
mstime_t master_link_down_time; /* Slave replication link down time. */
// 按照INFO命令输出的从节点优先级
int slave_priority; /* Slave priority according to its INFO output. */
// 故障转移时,从节点发送SLAVEOF <new>命令的时间
mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */
// 如果当前实例是从节点,那么保存该从节点连接的主节点实例
struct sentinelRedisInstance *master; /* Master instance if it's slave. */
// INFO命令的回复中记录的主节点的IP
char *slave_master_host; /* Master host as reported by INFO */
// INFO命令的回复中记录的主节点的port
int slave_master_port; /* Master port as reported by INFO */
// INFO命令的回复中记录的主从服务器连接的状态
int slave_master_link_status; /* Master link status as reported by INFO */
// 从节点复制偏移量
unsigned long long slave_repl_offset; /* Slave replication offset. */
/* Failover */
/*----------------------------------故障转移的属性----------------------------------*/
// 如果这是一个主节点实例,那么leader保存的是执行故障转移的Sentinel的runid
// 如果这是一个Sentinel实例,那么leader保存的是当前这个Sentinel实例选举出来的领头的runid
char *leader; /* If this is a master instance, this is the runid of
the Sentinel that should perform the failover. If
this is a Sentinel, this is the runid of the Sentinel
that this Sentinel voted as leader. */
// leader字段的纪元
uint64_t leader_epoch; /* Epoch of the 'leader' field. */
// 当前执行故障转移的纪元
uint64_t failover_epoch; /* Epoch of the currently started failover. */
// 故障转移操作的状态
int failover_state; /* See SENTINEL_FAILOVER_STATE_* defines. */
// 故障转移操作状态改变的时间
mstime_t failover_state_change_time;
// 最近一次故障转移尝试开始的时间
mstime_t failover_start_time; /* Last failover attempt start time. */
// 更新故障转移状态的最大超时时间
mstime_t failover_timeout; /* Max time to refresh failover state. */
// 记录故障转移延迟的时间
mstime_t failover_delay_logged; /* For what failover_start_time value we
logged the failover delay. */
// 晋升为新主节点的从节点实例
struct sentinelRedisInstance *promoted_slave; /* Promoted slave instance. */
/* Scripts executed to notify admin or reconfigure clients: when they
* are set to NULL no script is executed. */
// 通知admin的可执行脚本的地址,如果设置为空,则没有执行的脚本
char *notification_script;
// 通知配置的client的可执行脚本的地址,如果设置为空,则没有执行的脚本
char *client_reconfig_script;
// 缓存INFO命令的输出
sds info; /* cached INFO output */
} sentinelRedisInstance;
其中addr
属性是一
个指向 sentinel.c/sentinelAddr
结构的指针,这个结构保存实例的IP地址和端口号:
/* Address object, used to describe an ip:port pair. */
// 描述地址对象的结构
typedef struct sentinelAddr {
char *ip;
int port;
} sentinelAddr;
对 Sentinel 状态的初始化将引发对 masters
字典的初始化, 而 masters
字典的初始化是根据被载入的 Sentinel 配置文件来进行的。
2.5. 创建连向主服务器的网络连接
这一步是创建连向被监视主服务器的网络连接,Sentinel将成为主服务器的客户端,可以向主服务器发送命令,并从命令回复中获取相关的信息。
每个被Sentinel监视的主服务器,Sentinel会创建两个连向主服务器的异步网络连接:
命令连接,用于向主服务器发送命令,并接收命令回复
订阅连接,用于订阅主服务器的 __sentinel__:hello 频道
为什么有两个连接?
在 Redis 目前的发布与订阅功能中, 被发送的信息都不会保存在 Redis 服务器里面, 如果在信息发送时, 想要接收信息的客户端不在线或者断线, 那么这个客户端就会丢失这条信息。
因此, 为了不丢失
__sentinel__:hello
频道的任何信息, Sentinel 必须专门用一个订阅连接来接收该频道的信息。而另一方面, 除了订阅频道之外, Sentinel 还又必须向主服务器发送命令, 以此来与主服务器进行通讯, 所以 Sentinel 还必须向主服务器创建命令连接。
并且因为 Sentinel 需要与多个实例创建多个网络连接, 所以 Sentinel 使用的是异步连接。
接下来的一节将介绍 Sentinel 是如何通过命令连接和订阅连接来与被监视主服务器进行通讯的。
三 获取主服务器信息
Sentinel 默认每十秒一次,通过命令连接向被监视的主服务器发送 INFO 命令,并通过分析 INFO 命令回复来获取主服务器当前信息。两方面信息:
- 关于服务器本身的信息
包括 run_id 域记录的服务器运行ID,以及 role 域记录的服务器角色
- 关于主服务器属下的所有从服务器信息
每个从服务器都由一个“slave”字符串开头的行记录,每行的 ip= 域记录了从服务器的IP地址, port= 域记录了从服务器的端口号。根据这些IP地址和端口号,Sentinel无须用户提供从服务器的地址信息,就可以自动发现从服务器。
根据 run_id 域和 role 域的信息,Sentinel将对主服务器的实例结构进行更新。而主服务器返回的从服务器信息,将会被用于更新主服务器实例结构的 slaves 字典(记录了属下从服务器的名单)。
Sentinel 分析 INFO 命令中包含的从服务器信息时,会检查从服务器对应的实例结构是否已经存在于 slaves 字典: 如果存在,就对从服务器的实例结构进行更新,如果不存在(表明这个从服务器是新发现的从服务器),Sentinel会在 slaves 字典中为这个从服务器创建一个新的实例结构。
书上给了图,这里略过。注:
- 主服务器实例结构的
flags
值为SRI_MASTER
,从服务器是SRI_SLAVE
- 主服务器实例结构的
name
由用户使用Sentinel配置文件设置,从服务器的name
是由Sentinel根据服务器ip+port自动设置的
四 获取从服务器信息
当Sentinel发现主服务器有新的服务器出现时,除了会为这个新从服务器创建相应的实例结构之外,还会创建连接到从服务器的命令连接和订阅连接。
创建了命令连接之后,每10秒一次向从服务器发送 INFO 命令,并根据回复分析以下信息:
- 从服务器的运行ID run_id
- 从服务器的角色 role
- 主服务器的ip地址 master_host 以及主服务器的端口号 master_port
- 主从服务器的连接状态 master_link_status
- 从服务器的优先级 slave_priority
- 从服务器的复制偏移量 slave_repl_offset
- 根据这些信息,Sentinel会对从服务器的实例结构进行更新。
五 向主服务器和从服务器发送信息
默认情况下,sentinel每两秒一次,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:
PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,s_epoch>, <m_name>,<m_ip>,<m_port>,<m_epoch>"
这条命令就表示向服务器的 __sentinel__:hello
频道发送一条信息,信息由一下部分组成:
- 以
s_
开头的参数记录Sentinel本身的信息 - 以
m_
开头的参数则是主服务器的信息,当然如果监视的是从服务器,这个信息表示的就是从服务器的信息
下表是 该命令中的相关参数
参数 | 意义 |
s_ip | Sentinel的ip地址 |
s_port | Sentinel的端口号 |
s_runid | Sentinel的运行ID |
s_epoch | Sentinel当前的配置纪元 |
m_ip | 主服务器的ip地址 |
m_port | 主服务器的端口号 |
m_runid | 主服务器的运行ID |
m_epoch | 主服务器当前的配置纪元 |
六 接收来自主服务器和从服务器的频道信息
在建立起订阅连接之后,Sentinel会通过这个连接,向服务器发送以下命令:
SUBSCRIBE __sentinel__:hello
Sentinel对 __sentinel__:hello
这个定于会一直持续到Sentinel与服务器的连接断开之后。
就是说sentinel 可以通过这个频道发送和接收信息。
对于监视同一服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他的Sentinel接收到,并用于更新其他Sentinel对发送信息Sentinel的认知,和被用于更新其他Sentinel对被监视服务器的认知(收到自己发送的会被忽略掉)。
6.1 更新sentinels字典
sentinels
字典保存了除Sentinel本身之外,所有同样监视这个主服务器的其他Sentinel资料:
- 键 是其中一个Sentinel的名字,格式:
ip:port
- 值 是对应Sentinel的实例结构
当一个Sentinel接收到其他Sentinel发来的信息时(称发送信息的Sentinel为源Sentinel,接收信息的Sentinel为目标Sentinel),目标Sentinel会从信息中分析出以下信息:
与Sentinel相关的参数:源Sentinel的IP、port、run_id、配置纪元
与主服务器相关参数:源Sentinel 正在监视的主服务器的名字、IP、port、配置纪元
根据这些主服务器参数,目标Sentinel会在自己的Sentinel状态的 masters 字典中查找相应的主服务器实例结构,然后根据提出的Sentinel参数,检查主服务器实例结构的 sentinels 字典中,源 Sentinel的实例结构是否存在:
- 存在,就对源Sentinel的实例结构进行更新
- 不存在,说明源Sentinel是才开始监视主服务器的新Sentinel,目标Sentinel会为源Sentinel创建一个新的实例结构,并将这个结构添加到 sentinels 字典里面
因为一个sentinel可以通过分析接受的频道信息知道其它sentinel的存在,也可以通过发送频道信息让其它sentinel知道自己的存在,所以用户不需要提供各个sentinel的地址监视同一主服务器的多个sentinel 可以自动发现对方。
6.2 创建连向其他Sentinel的命令连接
当Sentinel通过频道信息发现了一个新的Sentinel时,它不仅会为新的Sentinel在 sentinels 字典中创建相应的实例结构,还会创建一个连向新Sentinel的命令连接。新的Sentinel同样会创建连向这个Sentinel的命令连接,最终监视同一主服务器的多个Sentinel将形成相互连接的网络:SentinelA有连向SentinelB的命令连接,SentinelB也有连向SentinelA的命令连接。
使用命令连接的各个sentinel 可以通过向其它sentinel发送命令来进行信息交换。下面介绍的服务器监视下线就是使用sentinel之间的命令通信。
Sentinel之间不会创建订阅连接:靠订阅主、从服务器的频道信息,已经能获取未知sentinel。而sentinel之间通信靠命令通信足够。
七 检测主观下线
默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他Sentinel在内)发送 PING 命令,并通过实例返回的 PING 命令回复来判断实例是否在线。
两种实例对 PING 指令的回复情况:
- 有效回复:实例返回 +PONG 、 -LOADING 、-MASTERDOWN 三种其中一种
- 无效回复,除了上面三种之外的其它回复,或者在指定时限内没有返回任何回复
Sentinel配置文件中的 down-after-millseconds 选项指定了Sentinel判断实例进入主观下线所需的时间长度:如果一个实例在 down-after-millseconds 毫秒内,连续向Sentinel返回无效回复,那么Sentinel会修改这个实例所对应的实例结构,在结构的 flags 属性中打开 SRI_S_DOWN 标识,用于表示这个实例已经进入主观下线状态。
1. 主观下线时长选项,即 down-after-down 的值,不仅会被Sentinel用于判断主服务器的主观下线状态,还会被用于判断主服务器属下的所有从服务器,以及所有同样监视这个主服务器的其他Sentinel的主观下线状态。
2. 多个Sentinel设置的主观下线时长可能不同,对于监视同一个主服务器的多个Sentinel来说,这些Sentinel设置的 down-after-milliseconds 选项的值可能不同,因此,当一个Sentinel将主服务器判断为主观下线时,其它Sentinel可能任然会认为主服务器处于在线状态。
八 检测客观下线
当Sentinel将一个主服务器判断为主观下线之后,为确定这个服务器是否真的下线,它会向同样监视这个主服务器的其它Sentinel进行询问,当接收到足够数量的已下线判断之后,Sentinel就会将从服务器判定为客观下线,并对主服务器进行故障转移操作。
8.1. 发送 SENTINEL is-master-down-by-addr
命令
Sentinel会发送下面的命令询问其它Sentinel是否同意主服务器下线:
SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
下表是参数的意义:
参数 | 意义 |
ip | 被Sentinel判断 为主观下线的主服务器ip |
port | 被Sentinel判断 为主观下线的主服务器端口号 |
current_epoch | Sentinel当前的配置纪元,用于选举领头Sentinel |
runid | 可以是 *符号或者Sentinel的运行ID,* 表示命令仅仅用于检测主服务器的客观下线状态,而Sentinel的运行ID则用于选举领头Sentinel |
8.2. 接收 SENTINEL is-master-down-by-addr 命令
当一个Sentinel(目标Sentinel)接收到另外一个Sentinel(源Sentinel)发来的 SENTINEL is-master-by-addr 命令时,目标Sentinel会分析并取出命令请求中包含的各个参数,并根据其中的IP和port,判断主服务器是否已经下线,然后向源Sentinel返回一个包含三个参数的 Multi Bulk 回复作为这个命令的回复。
<down_state> | 返回目标Sentinel对主服务器的检查结果,1表示主服务器已下线,0表示主服务器未下线 |
<leader_runid> | 可以是 * 符号或者目标Sentinel的局部领头Sentinel的运行ID,*表示命令仅仅用于检测主服务器的下线状态, 而局部领头Sentinel的运行ID则用于选举领头Sentinel |
<leader_epoch> | 目标Sentinel的局部领头Sentinel的配置纪元,用于选举领头Sentinel。 仅在 leader_runid 值不为 * 时有效,如果其值为 * ,这个参数总为0 |
8.3. 接收 SENTINEL is-master-down-by-addr 命令的回复
根据其他Sentinel发回的 SENTINEL is-master-down-by-addr 回复,Sentinel将统计同意主服务器下线的数量,当这个值达到配置指定的判断客观下线所需的数量时(即 quorum 属性的值),Sentinel会将主服务器实例结构中 flags 属性的 SRI_O_DOWN 标识打开,标识主服务器已经进入客观下线状态。
注意:不同sentinel可能客观判断下线条件不一样。
九 选举领头sentinel
当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器进行故障转移操作。
如何选举领头Sentinel的规则和方法:
1.每个做主观下线的sentinel节点向其他sentinel节点发送上面那条命令,要求将它设置为领导者。
2.收到命令的sentinel节点如果还没有同意过其他的sentinel发送的命令(还未投过票),那么就会同意,否则拒绝。
3.如果该sentinel节点发现自己的票数已经过半且达到了quorum的值,就会成为领导者
4.如果这个过程出现多个sentinel成为领导者,则会等待一段时间重新选举。
十 故障转移
在选举产生出领头Sentinel之后,领头Sentinel将对已下线的主服务器进行故障转移操作:
1在已下线的主服务器属下的所有从服务器中,挑选一个从服务器作为主服务器
挑选一个状态良好、数据完整的从服务器,然后发送 SLAVEOF no one 命令,然后将这个从服务器转换成主服务器
- 删除列表中所有已经下线的或者短线状态的从服务器。(保证从服务器都是在线的)
- 删除列表中最近5秒内没有回复过头sentinel的INFO命令的从服务器(保证从服务器都是最近通信过的)
- 删除所有与已下线主服务器连接断开超过 阈值的从服务器,保证剩余从服务器数据都是比较新的。再看如何排序选择剩余的从服务器。
- 选择slave-priority(slave节点优先级配置)最高的slave节点,(默认都是一样的)例如:如果我们有两台slave在两台机器上,一台配置较高,我们希望当master挂掉优先选配置高的,就可以配置该值为slave中最高的。如果存在最高则返回,不存在继续
- 选择复制偏移量最大的节点(复制得最完整,与master节点的数据一致性更高),如果存在则返回,不存在继续
- 如果以上两个条件都不满足,选runId最小的(启动最早的)
2让已下线的主服务器的所有从服务器改为复制新的主服务器
3将已下线主服务器设置为新的主服务器的从服务器,这个旧的主服务器重新上线时,就会成为新的主服务器的从服务器。