前言
本文将对Redis
的高可用方案———哨兵模式进行介绍,讲解其内部实现原理。
概述
Sentinel
是 Redis
的高可用性解决方案:由一个或多个 Sentinel
实例组成的 Sentinel
系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
如上图为一个 Sentinel
系统实例:
- server1 为当前的主服务器
- server2、server3、server4 为主服务器的三个从服务器
- 三个从服务器正在复制主服务器,而
Sentinel
系统则在监视所有服务器
假设这时,主服务器 server1 进入下线状态,那么三个从服务器对主服务器的复制操作将被终止,并且 Sentinel
系统会察觉到 server1 已下线。
当主服务器server1的下线时长超过用户设定的下线时长上限时,Sentinel
系统会对 server1 执行故障转移操作:
- 首先,
Sentinel
系统会挑选server1属下的其中一个从服务器,并将这个被选中的从服务器升级为新的主服务器 - 之后,
Sentinel
系统会向server1属下的所有从服务器发送新的复制指令,让它们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕。 - 另外,
Sentinel
还会继续监视已下线的server1,并在它重新上线时,将它设置为新的主服务器的从服务器。
启动及初始化Sentinel
启动一个Sentinel
实例可使用以下命令其一即可:
redis-sentinel /etc/redis/sentinel.conf
redis-server /etc/redis/sentinel.conf
当一个 Sentinel
实例启动时,需要执行以下步骤:
- 初始化服务器
- 将普通
Redis
服务器使用的代码替换成Sentinel
专用代码 - 初始化
Sentinel
状态 - 根据给定的配置文件,初始化
Sentinel
的监视主服务器列表 - 创建连向主服务器的网络连接(网络连接又包括:命令连接、订阅连接)
哨兵原理
获取主服务器信息
Sentinel
默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送 INFO
命令,并通过分析 INFO
命令的回复来获取主服务器的当前信息。
通过 INFO
命令回复,Sentinel
可以获取以下两方面的信息:
- 一方面是关于主服务器本身的信息,包括
run_id
域记录的服务器运行 ID,以及role
域记录的服务器角色 - 另一方面是关于主服务器属下所有从服务器的信息,每个从服务器都由一个
slave
字符串开头的行记录,Sentinel
与主从服务器的通信,每行的ip=域记录了从服务器的地址,而port=域则记录了从服务器的端口号。根据这些IP地址和端口号,Sentinel无须用户提供从服务器的地址信息,就可以自动发现从服务器。
至于主服务器返回的从服务器信息,则会被用于更新主服务器实例结构的slaves
字典,这个字典记录了主服务器属下从服务器的名单:
Sentinel
在分析INFO
命令中包含的从服务器信息时,会检查从服务器对应的实例结构是否已经存在于slaves字典:
- 如果从服务器对应的实例结构已经存在,那么
Sentinel
对从服务器的实例结构进行更新。 - 如果从服务器对应的实例结构不存在,那么说明这个从服务器是新发现的从服务器,
Sentinel
会在slaves
字典中为这个从服务器新创建一个实例结构。
对于一个master
和三个slave
的例子来说,Sentinel
将分别为三个从服务器创建他们各自的实例结构,并将这些结构保存到主服务器实例结构的 slaves
字典里,如图所示。
图中主服务器实例结构和从服务器实例结构之间的区别:
- 主服务器实例结构的flags属性的值为
SRI_MASTER
,而从服务器实例结构的flags属性的值为SRI_SLAVE
- 主服务器实例结构的name属性的值是用户使用
Sentinel
配置文件设置的,而从服务器实例结构的name属性的值则是Sentinel
根据从服务器的IP地址和端口号自动设置的
获取从服务器信息
当Sentinel
发现主服务器有新的从服务器出现时,Sentinel
除了会为这个新的从服务器创建相应的实例结构之外,Sentinel
还会创建连接到从服务器的命令连接和订阅连接。
如图所示,Sentinel
与各个从服务器建立命令连接和订阅连接。
向所有主从服务器发送信息
在默认情况下,Sentinel
会以每两秒一次的频率向所有被监视的主服务器和从服务器发送以下命令:
PUBLISH __sentinel_:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
信息的内容由多个参数组成:
- s_ip:
Sentinel的IP地址
- s_port:
Sentinel的Port
- s_runid:
Sentinel的运行ID
- s_epoch:
Sentinel当前的配置纪元
- m_name:主服务器名称
- m_ip:主服务器IP地址
- m_port:主服务器Port
- m_epoch:主服务器当前的配置纪元
接收来自主从服务器的信息
当Sentinel
与一个主服务器或者从服务器建立起订阅连接之后,Sentinel
就会通过订阅连接,向服务器发送以下命令:
SUBSCRIBE_sentinel_:hello
Sentinel
对频道的订阅会持续到 Sentinel
与服务器的连接断开为止。
这也就是说,对于每个与Sentinel
连接的服务器,Sentinel
既通过命令连接向服务器的sentinel_:hello
频道发送信息,又通过订阅连接从服务器的_sentinel_:hello
频道接收信息,如图所示。
对于监视同一个服务器的多个Sentinel
来说,一个Sentinel
发送的信息会被其他Sentinel
接收到,这些信息会被用于更新其他Sentinel
对发送信息Sentinel
的认知,也会被用于更新其他Sentinel
对被监视服务器的认知。
假设现在有三个 Sentinel
在监视同一个服务器,那么当服务器Sentinel1
向频道发送一条信息时,所有订阅了此频道的Sentinel
都会收到这条信息。如下图所示。
检测主观下线状态
在默认情况下,Sentinel
会以每秒一次的频率向所有与它创建了命令连接的实例包括主服务器、从服务器、其他Sentinel
在内发送PING命令,并通过实例返回的PING
命令回复来判断实例是否在线。
其中,有效回复:
- +PONG
- +LOADING
- -MASTERDOWN
无效回复为除了有效的三种回复外的其他回复。
Sentinel
配置文件中的down-after-milliseconds
选项指定了Sentinel
判断实例进入主观下线所需的时间长度:如果一个实例在down-after-milliseconds
毫秒内,连续向Sentinel
返回无效回复,那么Sentinel
会修改这个实例所对应的实例结构,在结构的flags
属性中打开SRI_S_DOWN
标识,以此来表示这个实例已经进入主观下线状态。
检测客观下线状态
当Sentinel
将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他Sentinel
进行询问,看它们是否也认为主服务器已经进入了下线状态(可以是主观下线或者客观下线)。当Sentinel
从其他Sentinel
那里接收到足够数量的已下线判断之后,Sentinel
就会将从服务器判定为客观下线,并对主服务器执行故障转移操作。
SENTINEL is-master-down-by-addr
命令
发送命令
Sentinel
使用 SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
命令询问其他 Sentinel
是否同意主服务器已下线,命令中的各参数:
- ip:被
Sentinel
判断为主观下线的主服务器的IP地址 - port:被
Sentinel
判断为主观下线的主服务器的端口号 - current_epoch:
Sentinel
当前的配置纪元,用于选举领头Sentinel
- runid:可以是*符号或者
Sentinel
的运行ID,*符号代表命令仅仅用于检测主服务器的客观下线状态,而Sentinel
的运行ID则用于选举领头Sentinel
接收命令
当一个 Sentinel
(目标Sentinel
)接收到另一个Sentinel
(源Sentinel
)发来的SENTINEL is-master-down-by-addr
命令时,目标Sentinel
会分析并取出命令请求中包含的各个参数,并根据其中的主服务器IP和端口号,检查主服务器是否已下线,然后向源Sentinel
返回一条包含三个参数的Multi Bulk
回复作为SENTINEL is-master-down-by-addr
命令的回复:
- <down_state>:返回目标
Sentinel
对主服务器的检查结果,1:已下线,0:未下线 - <leader_runid>:可以是*符号或者目标
Sentinel
的局部领头Sentinel
的运行ID - <leader_epoch>:目标
Sentinel
的局部领头Sentinel
的配置纪元,用于选举领头Sentinel
,如果<leader_runid>为*,那么<leader_epoch>总为0
接受命令回复
根据其他Sentinel
发回的SENTINEL is-master-down-by-addr
命令回复,Sentinel
将统计其他Sentinel
同意主服务器已下线的数量,当这一数量达到配置(Sentinel配置中的设置的quorum参数值)指定的判断客观下线所需的数量时,Sentinel
会将主服务器实例结构flags属性的SRI_O_DOwN
标识打开,表示主服务器已经进入客观下线状态。
对于监视同一个主服务器的多个Sentinel
来说,它们将主服务器判断为客观下线的条件可能也不同:当一个Sentinel
将主服务器判断为客观下线时,其他Sentinel
可能并不是那么认为的。比如说,对于监视同一个主服务器的五个Sentinel
来说,如果Sentinel1
在启动时载入了以下配置:
sentinel monitor master 127.0.0.1 6379 2
那么当五个 Sentinel
中有两个 Sentinel
认为主服务器已经下线时,Sentinel1
就会将主服务器判断为客观下线。
而对于载入了以下配置的 Sentinel2
来说:
sentinel monitor master 127.0.0.1 6379 5
仅有两个Sentinel
认为主服务器已下线,并不会令 Sentinel2
将主服务器判断为客观下线。
选举领头Sentinel
当一个主服务器被判断为客观下线后,监视这个下线主服务器的各个Sentinel
会进行协商,选举出一个领头Sentinel
,并由领头Sentinel
对下线主服务器执行故障转移操作。
Redis
是如何选举出领头Sentinel
的?
所有在线的Sentinel
都有被选为领头Sentinel
的资格,如果有某个Sentinel
被半数以上的Sentinel
设置成了局部领头Sentinel
,那么这个Sentinel
称为领头Sentinel
,例如,在一个由10个Sentinel
组成的Sentinel
系统里,只要有大于等于 10/2+1=6个Sentinel
将某个Sentinel
设置为局部领头Sentinel
,那么被设置的那个Sentinel
就会称为领头Sentinel
每次进行选举后,不论选举是否成功,所有Sentinel
的配置纪元的值都会自增一次,实际上,配置纪元只是一个计数器。
如果在给定时限里,没有一个Sentinel
被选举为领头Sentinel
,那么各个Sentinel
将在一段时间之后再次进行选举,直到选出领头Sentinel
为止。
故障转移
在选举产生出领头Sentinel
之后,领头Sentinel
将对已下线的主服务器执行故障转移操作,该操作包含以下三个步骤:
1)在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器。
2)让已下线主服务器属下的所有从服务器改为复制新的主服务器。
3)将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。
选举出新的主服务器
故障转移第一步就是要在已下线主服务器属下的所有从服务器中,挑选出一个状态良好、数据完整的从服务器,然后向这个从服务器发送 SLAVEOF no one
命令,将这个从服务器转换为主服务器。
在一次故障转移操作中,领头 Sentinel
向被选中的从服务器server2发送SLAVEOF no one
命令, 在发送SLAVEOF no one
命令之后,领头Sentinel
会以每秒一次的频率(平时是每十秒一次),向被升级的从服务器发送INFO
命令,并观察命令回复中的角色(role)信息,当被升级服务器的role从原来的slave
变为master
时,领头Sentinel
就知道被选中的从服务器已经顺利升级为主服务器了。
修改从服务器的复制目标
当新的主服务器出现之后,领头Sentinel
下一步要做的就是,让已下线主服务器属下的所有从服务器去复制新的主服务器,这一动作可以通过向从服务器发送SLAVEOF
命令来实现。
展示了在故障转移操作中,领头Sentinel
向已下线主服务器serverl的两个从服务器server3和server4发送SLAVEOF
命令,让它们复制新的主服务器server2的例子。
将旧的主服务器变为新从服务器
故障转移最后一步,将已下线的主服务器设置为新的主服务器的从服务器。
因为旧的主服务器已经下线,所以这种设置是保存在 server1 对应的实例结构里的,当 server1 重新上线后,Sentinel
会向它发送 SLAVEOF
命令,让它成为 server2 的从服务器。
小结
哨兵会定时执行下面3个操作:
-
Sentinel
以每10秒一次的频率向被监视的主从服务器发送INFO命令,当主服务器处于下线状态,或者Sentinel
正在对主服务器进行转移时,Sentinel
向从服务器发送INFO命令的频率会改为每秒一次。 -
Sentinel
每2秒向主数据库和从数据库的_sentinel_:hello 频道发送自己的信息 -
Sentinel
每秒会向主数据库、从数据库和其他哨兵节点发送PING命令
本文主要介绍了Redis
的高可用方案之一:Sentinel
模式。如对Redis
感兴趣,可继续关注本专栏。