一.概述
Sentinel(哨岗或哨兵)是Redis的高可用解决方案:由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
1.1 下面是一个Sentinel系统与主从服务器之间的关系:
(1) 双环图案的server1是当前的主服务器,以及server2、server3、server4三个从服务器。
(2) 三个从服务器复制主服务器的写入命令数据,实现数据同步。
(3) Sentinel系统则监视所有四个服务器。
1.2 Sentinel系统监视主服务器下线
当主服务器server1进入下线状态,那么三个从服务器复制操作被中止,由Sentinel系统监视到了server1已下线,当server1的下线时长超过用户设定的下线时长时,Sentinel系统就会对server1执行自动故障迁移。下面是Sentinel系统监视到了server1主服务器已下线:
只有具备故障转移功能,才是一个高可用的解决方案。Sentinel系统自动执行故障转移步骤:
(1) 首先Sentinel系统会挑选一个从服务器,并将这个选中的从服务器升级为新的主服务器。
(2) 之后 Sentinel系统会让所有从服务器都开始复制新的主服务器,故障转移操作执行完毕。
(3) Sentinel系统还会继续监视已下线的server1,并在它重新上线时,将它设置为从服务器。
二. Sentinel服务器初始化过程介绍
2.1 初始化一个普通的Redis服务器
因为Sentinel服务器本质上只是一个运行在特殊模式下的Redis服务器,所以启动Sentinel的第一步,就是初始化一个普通的Redis服务器。初始化服务器在介绍“服务器章节”中讲到过。由于工作不同,Sentinel的初始化过程和普通Redis服务器的初始化过程并不完全相同。比如:Sentinel就不会载入RDB文件或者AOF文件。下面表格展示Sentinel模式下运行时,服务器各个主要功能使用情况:
功能 | 使用情况 |
数据库和键值对方面的命令,如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函数会调用sentinel.c/sentinelTimer函数,后者包含了Sentinel要执行的所有操作 |
2.2 服务器内部使用Sentinel专用代码
启动Sentinel的第二个步骤就是将一部分普通Redis服务器使用的代码替换成Sentinel专用代码。比如:普通Redis服务器端口6379,而Sentinel端口26379,即Sentinel.c/redis_sentinel_port常量。还有命令表也不一样,对于Sentinel的客户端就只有ping, sentinel,info, Subscribe,psubscribe,unsubscribe,punsubscribe 这7个命令,即Sentinel.c/sentinelcmds作为服务器的命令表。
2.3 初始化Sentinel状态
应用了专用代码之后,步骤三是服务器会初始化一个Sentinel.c/sentinelstate结构,这个结构保存了服务器中所有和Sentinel功能有关的状态,对于服务器一般状态还是由redis.h/redisServer结构保存。
2.4 初始化Sentinel状态的masters属性
sentinel状态中的masters字典记录了所有被sentinel监视的主服务器的相关信息,其中字典的键是被监视的主服务器名字,而字典的值是被监视主服务器对应的sentinel.c/sentinelRedisInstance结构。每个sentinelRedisInstance实例结构代表监视一个Redis服务器实例,这个实例可以是主服务器,也可以是从服务器,或者另外一个sentinel服务器。对于sentinel状态的初始化将引发对masters字典的初始化,而masters字典的初始化是根据被该入的sentinel配置文件(sentinel.conf)来进行的。
2.5 创建连向主服务器的网络连接
初始化Sentinel的最后一步是创建连向被监听主服务器的网络连接,Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关信息。对于被Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接:
(1) 一是命令连接,专门用于向主服务器发送命令,并接收命令回复。比如sentinel向主服务器发送:info命令。
(2) 二是订阅连接,专门用于订阅主服务器的_sentinel_:hello频道。 比如sentinel向主,从,其它sentinel发送sentinel本身和主库信息。
下面是一个Sentinel监视两个主服务器master1和master2,创建命令连接和订阅连接图:
命令连接:是sentinel用来对主服务器发送命令,以此来与主服务器进行通信,所以sentinel必须向主服务器创建命令连接。订阅连接:是redis在发布与订阅功能 中,被发送的信息都不会保存在redis服务器中,如果信息发送时,想要接收的客户端不在线或者断线,那么这个客户端就会丢失这条信息,因为为了不丢失_sentinel_:hello频道的任何信息,sentinel必须专门用一个订阅连接来接收该频道的信息。
三. 获取服务器信息
3.1 Sentinel获取主服务器信息
Sentinel默认会以每10秒一次的频率,通过命令连接向主服务器发送info命令,通过分析info命令的回复来获取主服务器的当前信息,就像在上篇讲到的复制功能,在客户端输入info replication 命令一样,Sentinel可以获取以下两方面的信息:
(1) 关于主服务器本身的信息,包括服务器run_id,role的服务器角色。
(2) 关于所有从服务器的信息,每个从服务器都由一个slave字符串开头的行记录,记录了从服务器IP和端口(主服务器中有从库的配置信息)。
3.2 Sentinel获取从服务器信息
当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实例结构(sentinelRedisInstance)之外,Sentinel还会创建连接到从服务器的命令连接和订阅连接。Sentinel默认会以每10秒一次的频率通过命令连接从服务器发送info命令,通过分析info命令的回复来获取从服务器的当前信息。包括:从服务器运行run_ID、从服务器角色role、主服务器的ip及端口、主从服务器的连接状态master_link_status、从服务器的优先级slave_priority。
3.3 Sentinel向主从服务器发送信息
在默认情况下, Sentinel会以每2秒一次的频率,通过命令连接向,所有被监视的主服务器和从服务器发送以下格式的命令:
这条命令向服务器的_sentinel_:hello频道发送了一条信息,信息的内容由多个参数组成:
(1) 以s_开头以参数记录的是sentinel本身的信息。
(2) 而m_开头的参数记录的则是主服务器的信息,如果sentinel正在监视的是主服务器,那么这些参数就是主服务器的信息,如果sentinel正在监视的是从服务器,那么这些参数记录就是从服务器正在复制的主服务器的信息。
参数 | 描述 |
S_ip | Sentinel的ip地址 |
S_port | Sentinel的端口号 |
S_runid | Sentinel的运行ID |
S_epoch |
Sentinel 的当前配置纪元 |
m_name | 主服务器的名字 |
M_ip | 主服务器的IP地址 |
M_port | 主服务器的端口号 |
M_epoch | 主服务器的当前配置纪元 |
以下是一条sentinel通过publish命令向主服务器发送的信息示例:
这个示例中sentinel的ip地址为172.0.0.1端口号为26379, 运行ID为后面一串,当前纪元为0。主服务器的名字为mymaster,ip地址为127.0.0.1,端口号为6379, 当前纪元为0。
3.4 sentinel接收来自主服务器和从服务器的频道信息
当sentinel与一个主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送以下命令:subscribe_sentinel_:hello 。对于每个与Sentinel连接的服务器,Sentinel既通过命令连向服务器的_sentinel_:hello频道发送信息,又通过订阅连接从服务器的_sentinel_:hello频道接收信息。
当有三个sentinel,分别是sentinel1、sentinel2 、sentinel3。三个sentinel在监视同一个服务器,那么当sentinel1向服务器的_sentinel_:hello频道发送一条信息时,所有订阅了_sentinel_:hello频道的sentinel(包括sentinel1自己在内)都会收到这条信息。
当一个sentinel从_sentinel_:hello频道收到一条信息时,sentinel会对这条信息进行分析,提取出信息中sentinel 的 ip 、port、runID等8个参数,并进行以下检查:
(1) 如果信息中记录的sentinel运行ID和接收信息的sentinel运行ID相同,那么说明这条信息是sentinel自己发送的,sentinel将丢弃这条信息,不做进一步处理。
(2) 相反地,如果信息中记录的sentinel运行ID和接收信息的sentinel运行ID不相同,那说明这条信息监视同一个服务器的其它sentinel发来的,接收信息的sentinel将根据信息中的参数,对相应主服务器的实例结构进行更新。
3.5 sentinel更新自己的sentinels字典
sentinel为主服务器创建实例结构中的sentinels字典,保存了sentinel本身,还监视这个主服务器的其他sentinel的资料。当一个sentinel接收到其他sentinels发来的信息时,接收的sentinel会从信息中分析并提取出两方面参数:
(1)与sentinel有关的参数,包括sentinel的ip、port、runid、配置纪元。
(2)与主服务器有关的参数, 包括监视主服务器的ip、port、runid、配置纪元。
假设分别有三个sentinel: 127.0.0.1:26379、127.0.0.1:26380、127.0.0.1:26381。三个sentinel正在监视主服务器127.0.0.1:6379, 那么当127.0.0.1:26379这个sentinel接收到以下消息时:
这个sentinel将执行以下动作:
(1) 第一条信息发送者为自己,信息忽略。
(2) 第二条信息发送者为26381, sentinel会根据信息提取出内容,对sentinels字典中26381对应的实例结构进行更新。
(3) 第三条信息发送者为23680,同样更新字典中的23680对应的实例结构。
每个sentinel都有自己的一个sentinels字典, 对于26379的sentinel它的sentinels字典信息保存了26380和26381两个sentinel信息。其它sentinel也一样。
3.6 sentinel创建连向其他sentinel的命令连接
当sentinel通过频道信息发现一个新的sentinel时,不仅更新sentinels字典,还会创建一个连向sentinel命令连接,而新的sentinel也会创建连向这个sentinel的命令连接,最终监视同一个主服务器的多个sentinel将形成相互连接的网络。如下图所示: