06 redis的哨兵系统的工作流程
1 哨兵的概述
- sentinel [ˈsɛntənəl]
Sentinel(哨兵)定义:哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时选择新的master并将所有slave连接到新的master。
哨兵提供的功能:
- 监控功能
不断的检查master和slave是否正常运行。
master存活检测、 master与slave运行情况检测
- 通知功能
当被监控的服务器出现问题时, 向其他(哨兵间,客户端) 发送通知
- 自动故障转移
断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址
哨兵的注意点:哨兵是由奇数个redis服务器组成,只是不提供数据服务
2 哨兵实例的配置文件
2-1 sentinel.conf文件的内容
cat sentinel.conf | grep -v '#' | grep -v "^$"
port 26379
dir /tmp
sentinel monitor mymaster 127.0.0.1 6379 2 // 2台哨兵实例判断监视服务器为主观下线,则该监视服务器变为客观下线,判定为客观下线才能进行故障转移
sentinel down-after-milliseconds mymaster 30000 // 监视主服务器的无效回复时间达到30000ms,则判断该服务器主观下线
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
配置一:sentinel monitor
这个配置表达的是 哨兵节点定期监控 名字叫做
配置二:sentinel down-after-milliseconds
每个哨兵节点会定期发送ping命令来判断Redis节点和其余的哨兵节点是否是可达的,如果超过了配置的
配置三:sentinel parallel-syncs
当哨兵节点都认为主节点故障时,哨兵投票选出的leader会进行故障转移,选出新的主节点,原来的从节点们会向新的主节点发起复制,这个配置就是控制在故障转移之后,每次可以向新的主节点发起复制的节点的个数,最多为
配置四:sentinel failover-timeout
这个代表哨兵进行故障转移时如果超过了配置的
配置五: sentinel auth-pass
如果主节点设置了密码,则需要这个配置,否则哨兵无法对主节点进行监控。
2-2 哨兵实例的启动
redis-sentinel sentinel-端口号.conf
3 哨兵的工作原理
3-1 工作流程概述
问题:哨兵系统的组成和作用?
Sentinel系统由一个或多个Sentinel实例(本质是运行在特殊模式下的redis服务器)组成:
1) 可以监视任意多个主服务器,以及主服务器属下的所有从服务器。
2)当被监视的主服务器下线时(宕机或者主动维护),自动将下线主服务器所属的从服务器升级为主服务器。
注意点:作为Sentinel的服务器无法使用事务命令,脚本命令以及数据库操作命令等命令。
问题:哨兵需要支持哪些功能?
1 监控
2 通知
3 自动故障转移(Automatic failover)
4 配置提供(客户端可以通过连接哨兵实例获取到当前的master最新地址)
- Monitoring. Sentinel constantly checks if your master and replica instances are working as expected.
- Notification. Sentinel can notify the system administrator, or other computer programs, via an API, that something is wrong with one of the monitored Redis instances.
- Automatic failover. If a master is not working as expected, Sentinel can start a failover process where a replica is promoted to master, the other additional replicas are reconfigured to use the new master, and the applications using the Redis server are informed about the new address to use when connecting.
- Configuration provider. Sentinel acts as a source of authority for clients service discovery: clients connect to Sentinels in order to ask for the address of the current Redis master responsible for a given service. If a failover occurs, Sentinels will report the new address.
3-1 阶段1:哨兵实例连接建立阶段
3-1-2 哨兵在监控阶段的总体流程(关注三种实例连接建立过程 )
step1: 启动并初始化哨兵服务器(sentinel)
主要工作:初始化状态(sentinelState)信息并建立与主服务器的网络连接。
- 从图16-7可以看到服务器会创建一个sentinelState结构体,结构中设置一个master字典(key-value即master名称-sentinelRedisInstanc)去保留所监视的主服务器信息。
细分步骤:
1)初始化服务器
2)将普通Redis服务器程序代码替换为Sentinel专用程序代码(这个步骤使得sentinel服务器无法执行普通redis服务器的相关指令)
3)执行Sentinel的专用程序代码去初始化sentinelState状态(具体的状态见sentinel.c中的sentinelState结构体定义)
4)根据给定配置文件,初始化Sentinel主服务器列表(本质上就是初始化sentinelState结构体中的maser字典)
5)创建主服务器的网络连接,对于每个主服务器会创建2个异步网络连接分被是命令连接与订阅连接
- 命令连接:专门用于发送命令并接受命令回复(后续sentinel会使用该连接获取主服务器信息)
- 订阅连接:订阅主服务器的 sentinel hello 频道(后续sentinel服务器会使用该连接发现/同步其他sentinel信息)
/* Main state. */
struct sentinelState {
char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. */
uint64_t current_epoch; /* Current epoch. */
// ===============================每个哨兵服务器会维护一个主服务器字典========================================
dict *masters; /* Dictionary of master sentinelRedisInstances.Key is the instance name, value is the sentinelRedisInstance structure pointer. */
int tilt; /* Are we in TILT mode? */
int running_scripts; /* Number of scripts in execution right now. */
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. */
char *announce_ip; /* IP addr that is gossiped to other sentinels ifnot NULL. */
int announce_port; /* Port that is gossiped to other sentinels if non zero. */
unsigned long simfailure_flags; /* Failures simulation. */
int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script paths at runtime? */
} sentinel;
从代码中可以看到:状态定义中包含有一个master字典,这个字典记录了所有被sentinel监视的主服务器的相关信息。
- 字典的key是配置文件中主服务器的名称,字典的值是一个sentinelRedisInstance结构体。
sentinelRedisInstance结构体的源码
typedef struct sentinelRedisInstance {
int flags; /* See SRI_... defines */
char *name; /* Master name from the point of view of this sentinel. */
char *runid; /* Run ID of this instance, or unique ID if is a Sentinel.*/
uint64_t config_epoch; /* Configuration epoch. */
sentinelAddr *addr; /* Master host. */
instanceLink *link; /* Link to the instance, may be shared for Sentinels. */
mstime_t last_pub_time; /* Last time we sent hello via Pub/Sub. */
mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time
we received a hello from this Sentinel
via Pub/Sub. */
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. */
mstime_t down_after_period; /* Consider it down after that period. */
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. */
//============================维护监视该主服务器的哨兵字典=======================================
dict *sentinels; /* Other sentinels monitoring the same master. */
//============================ 维护一个从服务器字典===============================================
dict *slaves; /* Slaves for this master instance.*/
unsigned int quorum;/* Number of sentinels that need to agree on failure. */
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. */
int slave_priority; /* Slave priority according to its INFO output. */
mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */
struct sentinelRedisInstance *master; /* Master instance if it's slave. */
char *slave_master_host; /* Master host as reported by INFO */
int slave_master_port; /* Master port as reported by INFO */
int slave_master_link_status; /* Master link status as reported by INFO */
unsigned long long slave_repl_offset; /* Slave replication offset. */
/* Failover */
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. */
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. */
char *notification_script;
char *client_reconfig_script;
sds info; /* cached INFO output */
} sentinelRedisInstance;
注意:系统启动时根据配置文件中的信息初始化字典中每个master的信息,注意配置文件中需要设置监视master的ip与端口号
step2: 哨兵服务器初始化后,首次通过网络连接获取主服务器信息并更新
具体工作流程:哨兵服务器通过命令连接每10s一次发送info指令获取master信息以及属于该master服务器的slave服务器信息。
- 从服务器的信息在sentiRedisInstance的slave字典中,字典的key是从服务器的ip+端口,value还是sentiRedisInstance结构体。
注意点:
- master服务器信息与slave信息都是sentinelRedisInstance结构体的实例化
- 二者通过flags属性进行区分,都是通过字典进行维护,主服务器字典的key/name是用户通过配置文件设置的,而从服务器的name/key是该服务器的IP地址+端口号
step3:根据主服务器提供的从服务器信息,首次建立与从服务器的连接并获取信息
具体工作流程:利用主服务器提供的从服务器ip以及端口号,同样建立2个网络连接即命令连接以及订阅连接。建立连接后通过命令连接发送
info指令获取从服务器的详细信息并存储到对应的sentiRedisInstance实例结构。
主要更新的信息如下所示:
step4: 哨兵服务器向所有主从服务器频道发送信息并接受所有订阅的频道信息
具体流程:哨兵服务器会每隔2s一次向所有主从服务器的定于频道发送信息
- Redis Publish 命令用于将信息发送到指定的频道(下面的命令发送到名称为
__sentinel__:hello
的频道 )
注意点:
- 命令连接:哨兵服务器初次获取主从服务器发送指令是通过命令(cmd)连接
- 订阅连接:主从服务器会通过订阅连接向所有哨兵服务器发送自己的信息
监视同一个服务器的哨兵服务器通过接受解析订阅频道的信息识别到其他哨兵服务器的存在,然后哨兵服务器之间也会建立命令连接(哨兵服务器之间没有订阅连接)。!!!!!!!!!!!!!!!!!!!!!!!!
每个哨兵同时也会维护其他哨兵服务器的信息,这个信息存储在对应master状态中。
/* Master specific. */
//============================维护监视该主服务器的哨兵字典=======================================
dict *sentinels; /* Other sentinels monitoring the same master. */
//============================ 维护一个从服务器字典===============================================
dict *slaves; /* Slaves for this master instance.*/
3-1-3 连接建立阶段总结(重要)
单个哨兵启动 =>
实例初始化,利用配置文件中的master信息,建立与所有master的命令连接和订阅连接,通过命令连接发送info命令获取所有master信息
=> 利用之前获取的master信息与所有slave建立命令和订阅连接,同样使用info获取所有slave信息
=> 所有主从服务器订阅连接已经建立,此时哨兵服务器通过命令连接发送publish指令给主/从服务器的订阅频道,注意该哨兵服务器发送的信息也会被其他订阅同一频道的哨兵服务器(包括他自己)获取
=> 监控相同服务器的哨兵服务器通过订阅连接发送的频道信息(如果没有该信息,建立该哨兵服务器的命令连接),并维护该哨兵服务器的信息,至此整个redis服务器的连接建立完毕
=> 哨兵服务器周期性的:
a)通过命令连接向订阅频道发送public命令公开自己维护的信息
b)通过命令连接向其他三类实例发送ping命令确认他们的状态。
监控阶段更加详细的内容可以参考数据《redis设计与实现》第16章
3-2 阶段二:监控/通知
问题:哨兵实例会监控哪些信息?
答:监控并维护所监控的主服务器信息,从服务器信息以及其他哨兵服务器的信息,通过定期的ping命令确认状态,通过定期的info命令获取详细信息
通知:信息的长期维护阶段,sentinel服务器内部信息进行维护
- 对于监视同一个服务器的多个sentinel,一个sentinel发送的信息会被其他sentinel接受到,这些信息会被用于更新其他sentinel对发送信息sentinel信息的认识,也别用于更新其他sentinel对监视服务器的认识。
哨兵服务器之间信息的维护(结合上图):图中sentinel 1-3监视同一个服务器,当sentinel1通过命令(cmd)连接向监视服务器的__sentinel__:hello
channel发送信息,其他所有订阅该频道的sentinel(图中的2和3)也会收到该消息,这个消息中包含了当前监控服务器的配置,其他sentinel会对比较自己维护的配置是否比这个配置老,如果是的话则进行更新。
- 由于哨兵服务器之间通过订阅监视服务器的频道进行信息的维护,因此哨兵服务器之间不需要建立订阅连接。
Sentinels and replicas auto discovery
Sentinels stay connected with other Sentinels in order to reciprocally check the availability of each other, and to exchange messages. However you don't need to configure a list of other Sentinel addresses in every Sentinel instance you run, as Sentinel uses the Redis instances Pub/Sub capabilities in order to discover the other Sentinels that are monitoring the same masters and replicas.
This feature is implemented by sending hello messages into the channel named __sentinel__:hello
.
Similarly you don't need to configure what is the list of the replicas attached to a master, as Sentinel will auto discover this list querying Redis.
- Every Sentinel publishes a message to every monitored master and replica Pub/Sub channel
__sentinel__:hello
, every two seconds, announcing its presence with ip, port, runid. - Every Sentinel is subscribed to the Pub/Sub channel
__sentinel__:hello
of every master and replica, looking for unknown sentinels. When new sentinels are detected, they are added as sentinels of this master. - Hello messages also include the full current configuration of the master. If the receiving Sentinel has a configuration for a given master which is older than the one received, it updates to the new configuration immediately.
- Before adding a new sentinel to a master a Sentinel always checks if there is already a sentinel with the same runid or the same address (ip and port pair). In that case all the matching sentinels are removed, and the new added.
问题:为什么哨兵实例要与主从实例除了建立命令连接外,还建立订阅连接?
- 哨兵实例之间通过订阅连接获取信息实现内部的信息同步
- 哨兵实例的自动发现通过订阅连接实现,避免了哨兵实例手动去配置其他哨兵服务器的IP以及端口。
3-3 阶段三:故障转移阶段
背景知识:sential所监控的服务器的所有可能状态(sentinelRedisInstance的flag属性值)
/* A Sentinel Redis Instance object is monitoring. */
#define SRI_MASTER (1<<0) // 表示该实例是master服务器
#define SRI_SLAVE (1<<1) // 表示该实例是slave服务器
#define SRI_SENTINEL (1<<2) // 表示该实例是sentinel
#define SRI_S_DOWN (1<<3) /* Subjectively down (no quorum). 该实例是主观下线状态*/
#define SRI_O_DOWN (1<<4) /* Objectively down (confirmed by others).该实例是客观下线状态 */
#define SRI_MASTER_DOWN (1<<5) /* A Sentinel with this flag set thinks that
its master is down. */
#define SRI_FAILOVER_IN_PROGRESS (1<<6) /* Failover is in progress for
this master. */
#define SRI_PROMOTED (1<<7) /* Slave selected for promotion. */
#define SRI_RECONF_SENT (1<<8) /* SLAVEOF <newmaster> sent. */
#define SRI_RECONF_INPROG (1<<9) /* Slave synchronization in progress. */
#define SRI_RECONF_DONE (1<<10) /* Slave synchronized with new master. */
#define SRI_FORCE_FAILOVER (1<<11) /* Force failover with master up. */
#define SRI_SCRIPT_KILL_SENT (1<<12) /* SCRIPT KILL already sent on -BUSY */
step1主观下线判定阶段
默认情况下,sentinel会以1s一次的频率向所有与其建立命令连接的实例(包括主服务器,从服务器,其他sentinel)发送ping命令,并通过实例返回的ping命令回复判断实例是否在线。
- 当其中一台sentinel发现监控的服务器下线时,会将监控服务器的状态改为SRI_S_DOWN(主观下线)
问题:什么时候哨兵实例判断一台监控实例为主观下线?
答:当这台实例的长时间是无效回复(没有回复),到达配置文件设定的阈值,则判定为主观下线。
- 通过改变配置文件 sentinel.conf中的down-after-milliseconds选项设置主观下线判定等待的时间
注意:配置文件中master的down-after-milliseconds选项也会被用于其他类型的服务器实例。
step2:客观下线判定阶段
当超过半数的sentinel实例发现监控的服务器下线时,会将监控服务器的状态改为SRI_O_DOWN(主观下线)
- 判定客观下线的半数实例数目也是通过配置文件设置(通常是sentinel实例数目的一半+1)
step3:选举领头sentinel
详细的选举机制见《Redis的设计与实现》16.8节
step4:领头sentinel重新挑选master,并让其他slave连接这个新的master
3-4 哨兵的工作流程总结(重要)
哨兵实例的工作日志信息
1)连接建立阶段:
sentinel实例需要正常工作,首先基于哨兵配置文件建立与master实例的命令连接与订阅连接并获取slave的信息,之后建立与slave实例建立连接,然后通过
订阅频道获取其他哨兵的发布的信息,从而发现其他哨兵并建立连接。
2)监控/通知阶段:
--监控(同步监控服务器信息):sentinel通过命令连接发送ping指令确认其余三种实例的状态。
--通知(整合sentinel内部最新信息):通知可以理解为sentinel内部的最新信息同步,每个sentinel定期的publish自身的信息以及监控的服务器实例信息,这些信息会被所有订阅相同频道的1其他sentinel收到,当一个sentinel实例收到其他sentinel信息时,根据信息的版本来判断自己旧的信息是否过期并进行维护。
3)故障转移阶段
主客观下线判定maste下线=>竞选领头sentinel=>采用一定方式从slave选出新的master
=>发出命令使得新master上任,其他slave切换master,原master作为slave
哨兵实例总体工作流程(面试的时候连接建立可以不用太详细讲)
连接建立 => 监控/通知(信息维护) => 故障转移 => 监控/通知(信息维护) .....
哨兵实例发送与接受总结
- 常用的发送的指令
info: 获取主从服务器的详细信息
ping: 确认服务器的存活状态
publish:发布自身以及自己维护的最新信息
- 接受的常见信息
1)info/ping指令的反馈
2)订阅频道的信息(包含自己publish信息的反馈,以及其他哨兵实例publish信息的反馈)