Sentinel是Redis的高可用性解决方案,本文主要介绍Sentinel的初始化过程及其与一般Redis服务器的区别。并说明Sentinel监视服务器的方法和原理,说明Sentinel如何判断一个服务器是否在线,并介绍故障转移过程。
I、上帝视角看Sentinel
由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器替代已下线的主服务器继续处理命令请求。
Sentinel的主要功能就是监视主服务器是否下线,并进行相应处理(故障转移)。
下面四个示意图展示了Sentinel系统监视服务器过程:
1、server2、server3、server4 三个从服务器正在复制主服务器server1,而Sentinel系统监视这四个服务器:
2、此时,server1下线,从服务器会中止其复制状态:
3、当Sentinel系统判断server1的下线时长超过下线时长上限时,会为server1执行故障转移操作:
· 首先,Sentinel系统会挑选server1属下的其中一个从服务器(后面会介绍选择标准),将其升级为新的主服务器;
· 然后,Sentinel系统会向server1属下的所有从服务器发送新的复制指令,让它们成为新的主服务器(以server2为例)的从服务器;
· Sentinel系统还会继续监视已经下线的server1,并在它重新上线时,将其设置为server2的从服务器;
II、启动并初始化Sentinel
可以使用如下命令启动一个Sentinel:
$ redis-sentinel /path/to/your/sentinel.conf
或者
$ redis-server /path/to/your/sentinel.conf --sentinel
这时Sentinel系统会执行以下操作:
· 初始化服务器;
· 将普通Redis服务器使用的代码替换成Sentinel专用代码;
· 初始化Sentinel状态;
· 根据给定的配置文件,初始化Sentinel的监视主服务器列表;
· 创建连向主服务器的网络连接;
2.1 初始化服务器
Sentinel本质上只是一个运行在*特殊模式下8的Redis服务器,所以启动Sentinel的第一步就是启动一个普通的Redis服务器,具体步骤如Redis之服务器。
但是,Sentinel的工作与普通Redis有有所不同,所以在初始化过程中并不与初始化Redis服务器完全相同,如Sentinel不需要通过载入RDB文件或AOF文件还原数据库状态。
Sentinel与Redis普通服务器在使用功能上的主要不同:
2.2 使用Sentinel专用代码
普通Redis服务器使用redis.c/redisCommandTable
作为服务器命令表;
而Sentinel使用sentinel.c/sentinelcmds
作为服务器的命令表;
PING
, SENTINEL
,INFO
, SUBSCRIBE
, UNSUBSCRIBE
, PSUBSCRIBE
, PUNSUBSCRIBE
这七个命令为客户端可以对Sentinel执行的全部命令。
2.3 初始化Sentinel状态
服务器初始化一个sentinel.c/sentinelState
结构,这个结构保存了服务器中所有和Sentinel功能有关的状态(而服务器的一般状态由redis.h/redisServer
结构保存):
struct sentinelState {
//当前纪元,用于实现故障转移
uint64_t current_epoch;
//保存所有被这个sentinel监视的主服务器
//字典的键时主服务器的名字
//字典的值则是一个指向sentinelRedisInstance结构的指针
dict *masters;
//是否进入TILT模式
int tilt;
//目前正在执行的脚本数量
int running_scripts;
//进入TITL模式的时间
mstime_t tilt_start_time;
//最后一次执行时间处理器的时间
mstime_t previous_time;
//一个FIFO队列,包含了所有需要执行的用户脚本
list *scripts_queue;
} sentinel;
2.4 初始化Sentinel状态的masters属性
Sentinel状态中的masters字典记录了所有被Sentinel监视的主服务器的相关信息,其中:
· 字典的键为被监视主服务器的名字;
· 字典的值为主服务器对应的sentinel.c/sentinelRedisInstance
结构,这个结构代表一个被Sentinel监视的Redis服务器实例,这个实例可以是主服务器、从服务器或者另一个Sentinel。
下图说明了一个masters字典结构:
下图说明了一个sentinelRedisInstance结构:
· addr属性是一个指向sentinel.c/sentinelAddr
结构的指针,这个结构保存着实例的IP地址和端口号。
2.5 创建连向主服务器的网络连接
初始化Sentinel的最后一步是创建连向监视主服务器的网络连接,Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关信息。
对于每个Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接:
· 一个是命令连接;
· 一个是订阅连接,这个连接专门用于订阅主服务器的__sentinel__:hello
频道。这是因为Redis的发布与订阅功能中,被发送的信息不会保存在Redis服务器里面,如果在信息发送时,想要接收信息的客户端不在线,则这个客户端就会丢失这条信息。因此,为了不丢失__sentinel__:hello
频道的任何信息,Sentinel专门用一个订阅连接来接收该频道的信息。
下图展示了一个Sentinel向它监视的两个master创建网络连接的实例:
III、获取主服务器信息
Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送INFO
命令,并通过分析INFO命令的回复获取主服务器的当前信息。
通过分析主服务器返回的回复,Sentinel可以获取两方面的信息:
· 一方面是关于主服务器本身的信息,包括run_id域记录的服务器运行ID,以及role域记录的服务器角色。
· 另一方面会得到主服务器属下的所有从服务器的信息(主要包括从服务器的IP::port)。
主服务器返回的从服务器信息,会被用于更新主服务器实例结构的slaves字典,这个字典记录了主服务器属下从服务器的名单:
字典结构如下图所示:
IV、获取从服务器信息
Sentinel除了会为从服务器创建相应的实例结构外,还会创建连接到从服务器的命令连接和订阅连接
接下来,Sentinel会以每十秒一次的频率通过命令连接向从服务器发送INFO
命令,并获得相应的回复,这些回复包括:
· 从服务器运行ID run_id;
· 从服务器的角色 role;
· 主服务器的IP地址master_host,以及主服务器的端口号 master_port;
· 从服务器的优先级 slave_priority;
· 从服务器的复制偏移量 slave_repl_offset;
根据以上信息,Sentinel会对从服务器的实例结构进行更新:
V、向主服务器和从服务器发送信息
默认情况下,Sentinel会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送命令:
PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
这条命令向服务器的__sentinel__:hello
频道发送了一条信息,信息内容含义如下图:
VI、接收来自主服务器和从服务器的频道信息
当Sentinel与一个主服务器或从服务器建立起订阅连接之后,Sentinel会通过订阅连接,向服务器发送以下命令:
SUBSCRIBE __sentinel__:hello
也就是Sentinel对所有服务器的__sentinel__:hello
频道进行了订阅,这个订阅会一直持续到连接断开。
也就是说,对于每个与Sentinel连接的服务器,**Sentinel既通过命令连接向服务器的__sentinel__:hello
频道发送信息,又通过订阅连接从服务器的sentinel__:hello
频道接受信息:
可见,命令连接用于向服务器发送命令请求,而订阅连接用于接收指定频道的信息。
举个例子:
假设现在有sentinel1、sentinel2、sentinel3三个Sentinel在监视同一个服务器,那么当sentinel1向服务器的__sentinel__:hello
频道发送一条信息时(使用命令连接),所有订阅了__sentinel__:hello
频道的Sentinel都会收到这条信息:
之后Sentinel会对收到的订阅信息进行分析,提取出V中说明的八个参数,并进行检查:
· 如果信息中的Sentinel运行ID与自己的运行ID相同,说明这条信息是Sentinel自己发送的,Sentinel将丢弃这条信息,不做进一步处理。
· 如果不同,说明这条信息是监视同一个服务器的其他Sentinel发来的(这里要理解订阅信息是由谁发送的,订阅信息不是由服务器自己产生的,而是有客户端发送到服务器的,这里的客户端即是各个Sentinel),接受信息的Sentinel将根据信息中的各个参数,对相应主服务器的实例结构进行更新。
6.1 更新sentinels字典
Sentinel为主服务器创建的实例结构中的sentinels字典保存了除Sentinel本身之外,同样监视这个主服务器的其他Sentinel的资料:
而这些资料是由于所有监视某个主服务器的所有Sentinel都订阅了主服务器的__sentinel__:hello
频道,当Sentinel自己通过命令连接向主服务器发送如V中描述的PUBLISH
命令时,会通过主服务器的订阅连接广播给其他Sentinel。于是监视同一个主服务器的多个Sentinel可以自动发现对方。
6.2 创建连向其他Sentinel的命令连接
当Sentinel通过频道信息发现一个新的Sentinel时,不仅会为其在sentinels字典重创建实例,还会创建一个连向新Sentinel的命令连接,从而形成一个如下图的结构:
Sentinel之间的命令连接用来进行信息交换,实现如主观下线检测和客观下线检测等功能。
VII、检测主观下线状态
默认情况下,Sentinel会以每秒一次的频率向与它创建命令连接的实例(包括主服务器,从服务器,其他Sentinel)发送PING
命令,并通过实例返回的PING
命令回复来判断实例是否在线。
实例对PING
命令的回复可以分为一下两种情况:
· 有效回复: +PONG
,-LOADING
,-MASTERDOWN
;
· 无效回复: 上面三种回复之外的其他回复,或在指定时限内没有任何回复。
如果一个实例在配置的down-after-milliseconds
时间内,连续向Sentinel返回无效回复,那么Sentinel会修改这个实例对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,以此来表示这个实例已进入主观下线状态。
这里对于每个Sentinel给相同服务器的down-after-milliseconds
配置可能是不同的,所以当一个Sentinel将主服务器判断为主观下线时,其他Sentinel可能仍然会认为这个主服务器处于在线状态。
VIII、 检查客观下线状态
当Sentinel将一个主服务器判断为主观下线后,为了确认这个主服务器是否真的下线了,会向同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态(可能是主观下线或者客观下线)。当Sentinel从其他Sentinel得到足够多数量的下线判断后,Sentinel会将主服务器判定为客观下线,并对主服务器执行故障转移操作。
Sentinel使用SENTINEL is-master-down-by-addr
命令来与其他Sentinel交互,判断主服务器是否客观下线,如果达到配置的客观下线所需的数量时,Sentinel打开主服务器结构中的flags属性的SRI_O_DOWN
标识,标识主服务器进入客观下线状态。
IX、选举领头Sentinel
当一个主服务器被判断为客观下线后,检测这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线服务器执行故障转移操作。
选取领头Sentinel的规则和方法为:
X、故障转移
领头Sentinel对已下线的主服务器进行故障转移操作的步骤如下:
1、在已下线的主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器;
2、让已下线主服务器属下的所有从服务器改为赋值新的主服务器;
3、将已下线主服务器设置为新的主服务器的从服务器。
10.1 选出新的主服务器
1、新的主服务器的挑选过程如下:
2、实例:
假设根据以上规则挑选出server2为新的主服务器,则Sentinel向server2发送SLAVEOF no one
命令;
在发送完SLAVEOF no one
命令之后,领头Sentinel以每秒一次的频率(平时是每十秒一次)向server2发送INFO
命令,观察其role信息,当server2的role由slave变为master时,则server2顺利升级为主服务器。
10.2 修改从服务器的复制目标
领头Sentinel向server3和server4发送SLAVEOF
命令,令其复制新的主服务器server2的内容:
形成如下结构:
10.3 将旧的主服务器变为从服务器
当server1重新上线时,Sentinel向其发送SLAVEOF
命令,让其成为server2的从服务器:
【参考】
[1] 《Redis设计与实现》
欢迎转载,转载请注明出处wenmingxing Redis之Sentinel
网友评论