Sentinel是Redis的高可用性解决方案:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。
Sentinel系统启动及初始化流程
启动Sentinel
redis-sentinel /path/to/your/sentinel.conf
or
redis-server /path/to/your/sentinel.conf --sentinel
Sentinel本质上只是运行在特殊模式下的Redis服务器。在服务器的各个环节加载另外一套代码。
初始化Sentinel状态,加载Sentinel配置
struct sentinelState {
//当前纪元,用于实现故障转移
uint64_t current_epoch;
// 该sentinel监视的主服务器,
// 键是主服务器的名字
// 值是指针指向sentinelRedisInstance结构。
dict *masters;
int tilt;
int running_scripts;
mstime_t tilt_start_time;
mstime_t previous_time;
list *scripts_queue;
} sentinel;
sentinel监视的主服务器从配置文件中加载。
typedef struct sentinelRedisInstance {
// 记录实例的类型及当前状态
int flags;
// 实例名 = ip:port
char *name;
// 实例的运行ID
char *runid;
// 配置纪元
uint64_t config_epoch;
// 实例地址
sentinelAddr *addr;
// 该主服务器的从服务器
dict *slaves;
// 实例无响应多少毫秒后判定主观下线(subjectively down)
mstime_t down_after_period;
// 判断实例客观下线(objectively down)需要的投票数。
int quorum;
// 故障转移期间,可以同时对新的主服务器进行同步的从服务器数量
int parallel_syncs;
// 刷新故障迁移状态的最大时限
mstime_t failover_timeout;
} sentinelRedisInstance;
连接主服务器并获取主服务器信息
Sentinel与主服务器创建两个异步连接;
- 命令连接,向主服务器发送命令,接收回复。
- 订阅连接,订阅主服务器
_sentinel_:hello
频道。 // 发现其他sentinel并建立连接
==Sentinel默认每十秒向监视的主服务器发送INFO命令,主服务器将回复当前状态信息==:
Sentinel将分析INFO回复的信息,更新master dict的sentinelRedisInstance
以及其中的slaves字典。
连接从服务器并获取从服务器信息
当Sentinel通过INFO命令发现主服务器有新的从服务器出现时,还会创建到该服务器的命令连接和订阅连接。
==Sentinel同样通过向从服务器发送INFO命令,获取从服务器信息,并创建从服务器的sentinelRedisInstance。==
发送和接收被监视服务器上的订阅消息
向被监视服务器的_sentinel_:hello频道订阅消息:
默认每两秒一次发送:
PUBLISH _sentinel_:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
接收来自被监视服务器的频道订阅消息:
SUBSCRIBE _sentinel_:hello
所有监视同一个服务器的sentinel都能接收到其他sentinel发送的_sentinel_:hello
消息。
当接收到其他Sentinel的消息时,根据消息中的主服务器信息,找到对应的sentinelRedisInstance
结构,并更新其中Sentinels字典。
创建和其他Sentinel的命令连接
==当Sentinel通过频道订阅信息发现新的Sentinel时,不仅会更新sentinels字典,还会和该sentinel创建命令连接。==
Sentinel和服务器间建立命令连接和订阅连接。
Sentinel和其他Sentinel间建立命令连接。
Sentinel系统启动总结
1. 加载配置文件,保存监听主服务器信息。
2. 和主服务器建立命令连接和订阅连接。
3. 每10秒向主服务器发送INFO命令,获取主服务器及其从服务器信息。
4. 和从服务器建立命令连接和订阅连接。
5. 每两秒向_sentinel_:hello频道发送本sentinel及监听的主服务器信息。
6. 订阅_sentiel_:hello频道,获取其他sentinel信息并建立命令连接。
故障检测和转移
主观下线和客观下线
主观下线
- Sentinel默认每秒一次向所有创建了命令连接(包括主服务器、从服务器及其他Sentinel)的实例发送PING命令。
- 如果连续
down-after-milliseconds
时间未得到有效回复,对应主服务器被标记为主观下线。 down-after-milliseconds
在配置文件中配置,每个Sentinel配置的时间可能不同,因此==不同Sentinel对服务器的主观下线状态标记可能不同==。
客观下线
当Sentinel判断主服务器主观下线后, 会向监视该主服务器的其他Sentinel询问该主服务器下线状态:
SENTINEL is-master-down-by-addr <ip> <port> // 注意和选举时的is-master-down-by-addr的参数不一样
其他Sentinel将回复该主服务器的下线状态:
<down_state> <leader_runid> <leader_epoch>
Sentinel计算回复结果,如果回复已下线的Sentinel数量超过配置的quorum
值,则将该主服务器标记为客观下线。
和主观下线的down-by-milliseconds
参数一样,quorum
也是在配置文件中配置,并且每个Sentinel的配置可能不同,因此不同Sentinel的客观下线标记结果也可能不同。
Sentinel选举(==RAFT算法==)
-
所有Sentinel都有被选举为领头Sentinel的资格。
-
每次选举之后配置纪元(epoch)自增一。
-
在一个配置纪元里,==每个Sentinel都有一次且仅有一次将其他Sentinel设置为局部领头Sentinel的机会,一旦设置不能修改==。
-
每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel。
-
当一个Sentinel(源)向另一个Sentinel(目标)发送
SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
命令,且参数是源Sentinel的运行ID时,表示源Sentinel要求目标Sentinel设置为局部领头Sentinel。 -
设置局部领头Sentinel的规则是先到先得,之后到的设置要求会被目标Sentinel拒绝。
-
目标Sentinel回复中的
leader_runid
和leader_epoch
记录了目标Sentinel的局部领头Sentinel的运行ID和配置纪元。 -
源Sentinel接受到的回复中如果
leader_epoch
和leader_runid
都和自身一致,表明成功获得目标Sentinel的推选。 -
如果有某个Sentinel被半数以上的Sentinel推选(设置成局部领头Sentinel),这个Sentinel即成为领头Sentinel。
-
因为要得到半数以上的支持,所以一个配置纪元里只会有一个Sentinel。
-
如果给定时间内没有选出领头sentinel,一段时间后将再次进行选举,直到选出为止。
故障转移
选举出的领头Sentinel将对已下线的主服务器进行故障转移。
-
在已下线主服务器的所有从服务器中挑选出一个。
- 排除已断线从服务器。
- 排除最近五秒没有回复过INFO命令的从服务器。
- 排除与已下线主服务器连接断开超过
down-after-milliseconds
* 10毫秒的从服务器。 - 根据配置的从服务器==优先级==,选择优先级最高的。
- 对相同优先级的从服务器,选择==复制偏移量==最大的。
- 对相同优先级和复制偏移量的从服务器,选择==运行ID==最小的。
-
向选出的从服务器发送
slaveof no one
,转换为主服务器。发送
slaveof no one
,命令后,每秒一次发送INFO
,直到返回信息的role域变为master。 -
向其他所有从服务器发送
slaveof
命令,复制新的主服务器。 -
将旧的主服务器变为从服务器。
当旧的主服务器重新上线时,领头Sentinel会向其发送
slaveof
命令。
本文地址:https://cheng-dp.github.io/2019/03/28/sentinel/