Sentinel(哨兵)
Redis主从复制模式下,一旦主节点(主服务器)由于故障不能提供服务,需要人工将节点晋升为主节点,同时还要通知应用方更新主节点的地址,然而应用方无法及时感知到主节点的变化,必然会造成一定的写数据丢失和读数据错误,所以这是在大多数情况是无法接受的。所以Redis提供了一种高可用的解决方法——哨兵。
Sentinel是Redis的高可用解决方案:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态下时,自动将下线主服务器属下的某个从服务器升级为主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。如果下线的主服务器重新连接上线的话,它会被Sentinel系统降级为新主服务器的从服务器。而这些过程完全是自动的,不需要人工介。
下面介绍Sentinel系统的具体工作过程。
1 启动并初始化Sentinel
Sentinel只是一个运行在特殊模式下的Redis服务器,其本身就是独立的Redis节点,只不过它不存储数据,只支持部分命令。
Sentinel会读入用户指定的配置文件,为每个要被监视的主服务器创建相应的实例结构保存在Sentinel状态(服务器初始化的一个sentinel结构,用于保存服务器中所有和Sentinel功能有关的状态)的masters属性中。并创建连向主服务器的网络连接,Sentinel将成为主服务器的客户端,并从命令回复中获取相关信息。
对于每个被Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接:
1) 一个是命令连接,这个连接专门用于向主服务器的网络连接,并接受命令。
2) 另一个是订阅连接,这个连接专门用于订阅主服务器的_sentinel_:hello频道。
为什么有两个连接?
在Redis目前的发布与订阅功能中,被发送的信息都不会保存在Redis服务器里面,如果在信息发送时,想要接收信息的客户端不在线或者断线,那么这个客户端就会丢失 这条信息。 因此,为了不丢失_sentinel_:hello频道的任何信息, Sentinel必须 专门用一个订阅连接来接收该频道的信息。另一方面, 除了订阅频道之外, Sentinel还必须向主服务器发送命令, 以此来与主 服务器进行通信, 所以Sentinel还必须向主服务器创建命令连接。
因为Sentinel需要与多个实例创建多个网络连接, 所以Sentinel使用的是异步连接。
2 获取主服务器信息
Sentinel默认每10秒一次的频率,通过命令连接向被 监视的主服务器发送INFO命令,并通过INFO命令的回复来获取主服务器以下的信息:
(1) 服务器本身的信息,包括运行ID以及服务器的角色(role);
(2) 主服务属下的所有从服务器信息,包括从服务器的IP地址,端口号。根据这些IP和端口号,Sentinel无须用户提供从服务器的地址信息,就可以自动发现从服务器。
Sentinel根据这些获取的信息对主服务的实例结构进行更新。
对于上图,Sentinel将分别为3个从服务器创建各自的实例结构,并将这些实例结构保存主服务器实例结构的slaves属性里。
3 获取从服务器信息
当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实例结构外,Sentinel还会为创建连接到从服务器的命令连接和订阅连接。
在创建命令连接之后, Sentinel在默认情况下, 会以每10秒 一次的频率通过命令连接向从服务器发送INFO命令,并获取从服务器的回复信息,包括从服务器运行ID、从服务器的角色,主服务器的IP和端口、从服务的优先级等。根据这些信息对从服务器实例结构进行更新。
4 向主服务器和从服务器发送信息
在默认情况下,Sentinel会以每2秒一次的频率,通过命令连接向所有被监听的主服务器和从服务器的_sentinel_ hello频道发送一条消息。消息的信息包括Sentinel本身的信息和对主服务器判断的信息。
5 接收来自主服务器和从服务器的频道信息
当Sentinel与一个主服务器或者从服务器建立起订阅连接之后, Sentinel就会通过订阅连接, 向服务器发送以下命令:SUBSCRIBE _sentinel_:hello 。
Sentinel对_sentinel_:hello频道的订阅会一直持续到Sentinel与服务器的连接断开为止。
这也就是说, 对于每个与Sentinel连接的服务器, Sentinel既通过命令连接向服务器的 sentinel_:hello频道发送信息, 又通过订阅连接从服务器的 sentinel :hello 频道接收信息。
对于监视同一个服务器的多个Sentinel 来说, 一个Sentinel发送的信息会被其他 Sentinel接收到, 这些信息会被用于更新其他Sentinel对发送信息Sentinel的认知 ,也会被用于更新其他Sentinel对被监视服务器的认知。
举个例子, 假设现在有sentinel1、sentinel 2、sentinel 3三个Sentinel在监视同一个服务器, 那么当sentinel1向服务器的_sentinel_:hello频道发送一条信息时,所有订阅了_sentinel_:hello频道的Sentinel(包括sentinel1自己在内)都会收到这条信息。
当一个Sentinel从_sentinel_:hello频道收到一条信息时,Sentinel会对这条信息进行分析,提取出信息中的Sentinel IP地址、端口号、Sentinel运行ID等参数,并作以下检查:
**如果信息中记录的Sentinel运行ID和接收信息中Sentinel的运行ID 相同,说明这条信息是Sentinel自己发送的,Sentinel将丢弃这条信息,不做进一步处理。
**如果信息中记录的Sentinel运行ID和接收信息的Sentinel的运行ID不相同,那么说明这条信息是监视同一个服务器的其他Sentinel发来的, 接收信息的Sentinel 将根据信息中的各个参数, 对相应主服务器的实例结构进行更新。
这里的更新包括:主服务sentinels属性的更新和创建连向其他Sentinel命令连接
(1) Sentinel为主服务器创建的实例结构中的sentinels属性保存了除Sentinel本身之外,所有同样监视这个主服务器的其他Sentinel的资料。当一个Sentinel接收到其他Sentinel发来的信息时(我们称呼发送信息的Sentinel为源Sentinel, 接收信息的Sentinel为目标Sentinel),根据信息中提取出主服务器参数,目标Sentinel会在自己的Sentinel状态的masters字典中查找相应的主服务器实例结构, 然后根据提取出的Sentinel参数,检查主服务器实例结构的sentinels中,源Sentinel的实例结构是否存在:
如果源Sentinel的实例结构已经存在, 那么对源Sentinel的实例结构进行更新。
如果源Sentinel的实例结构不存在,那么说明源Sentinel是刚刚开始监视主服务器的新Sentinel, 目标Sentinel会为源Sentinel创建一个新的实例结构,并将这个结构添加到sentinels属性里面。
因为一个Sentinel可以通过分析接收到的频道信息来获取其他Sentinel的存在,并通过发送频道信息让其他Sentinel知道自己的存在,所以用户在使用Sentinel时不需要提供各个Sentinel的地址信息,监视同一个主服务器的多个Sentinel可以自动发现对方。
(2) 创建连向其他Sentinel命令连接
当Sentinel通过频道信息发现一个新的Sentinel时, 它不仅会为新Sentinel在sentinels中创建相应的实例结构, 还会创建一个连向新Sentinel的命令连接, 而新Sentinel也同样会创建连向这个Sentinel的命令连接, 最终监视同一主服务器的多个Sentinel将形成相互连接的网络。
Sentinel之间不会创建订阅连接
Sentinel在连接主服务器或者从服务器时,会同时创建命令连接和订阅连接,但是 在连接其他Sentinel时,却只会创建命令连接,而不创建订阅连接。 这是因为Sentinel 需要通过接收主服务器或者从服务器发来的频道信息来发现未知的新Sentinel, 所以才需要建立订阅连接,而相互已知的Sentinel只要使用命令连接来进行通信就足够了。
6 检测主观下线状态
在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令的连接实例(包括主服务器、从服务器、其他Sentinel在内),发送PING命令,并通过实例返回PING命令的回复判断实例是否在线,如果在规定的时间内,连续向Sentinel返回无效的回复(除了+PONG、-LOADING、-MASTERDOWN之外的回复),那么Sentinel会修改这个实例所对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,以此来标识这个实例已经进入了主观下线状态。
7 检查客观下线状态
当Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了, 它会向同样监视这一主服务器的其他Sentinel进行询问, 看它们是否也认为主服务器已经进人了下线状态(可以是主观下线或者客观下线)。 当Sentinel从其他Sentinel那里接收到足够数量的已下线判断之后, Sentinel就会将从服务器判定为客观下线, Sentinel会将主服务器实例结构flags属性的SRI_O_DOWN标识打开,标识主服务器已经进入了客观下线状态。
8 选举领头Sentinel
当一个主服务器被判断为客观下线时,监视这个下线的主服务器的各个Sentinel会进行协商,选举出以个领头Sentinel,并由领头Sentinel对下线主服务器执行故障转移操作。
选举领头Sentinel规则和方法:
**所有在线的Sentinel都有被选为领头Sentinel的资格。
**每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel。
**Sentinel设置局部领头Sentinel的规则是先到先得:最先向目标Sentinel发送设置要求的源Sentinel将成为目标Sentinel的局部领头Sentinel,而之后接收到的所有设置要求都会被拒绝。
**如果某个Sentinel被半数以上的Sentinel设置为局部领头Sentinel,那么这个Sentinel成为Sentinel。
**如果在给定的时限内,没有一个Sentinel被选举为领头Sentinel,那么各个Sentinel将在一段时间之后再进行选举,直到选出领头Sentinel为止。
注:上述只是选举领头Sentinel的大致思路。
下面两幅图表示当三个Sentinel发现主服务器已经进入客观下线状态后,为了选举出领头Sentinel,三个Sentinel将再次向其他Sentinel发送SENTINEL is-master-down-by-addr命令要求其他Sentinel将自己设置为局部领头Sentinel。根据先到先得规则,如果某个Sentinel发送的命令比其他的快,并最终胜出领头Sentinel的选举,然后这个领头Sentinel就可以开始对服务器执行故障转移操作了。
9 故障转移
在选举产生出领头Sentinel之后,领头Sentinel将对已下线的主服务器执行故障转移操作,该操作包含以下三个步骤:
1) 在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为 主服务器。
2) 让巳下线主服务器属下的所有从服务器改为复制新的主服务器。
3) 将已下线主服务器设置为新的主服务器的从服务器, 当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器
(1) 选出新的主服务器
故障转移的第一步就是在已下线主服务器属下的所有从服务器中挑选出一个状态良好、数据完成整的从服务器,然后向这个从服务器发送slave no one命令,将这个从服务器装换为主服务器。
新的主服务器是怎么挑选出来的
领头Sentinel会将已下线的主服务器的所有从服务器存到一个列表里,然后按照以下规则一条条过滤:
1) 过滤掉列表中所有下线或断线状态的从服务器。
2) 过滤掉列表中所有最近5秒内没有回复过领头Sentinel的INFO命令的从服务器。
3) 过滤掉所有与已下线的主服务连接断开超过规定时长的从服务器,这是为了保证列表中剩余服务器没有过早的与主服务器断开连接,保证列表中从服务器数据都是比较新的。
4) 之后,按照从服务器的优先级,对列表中剩余的从服务器排序,选出优先级最高的从服务器;如果存在相同的,则比较从服务器的复制偏移量,选出其中较大的;如果还是存在相同的,则选出运行ID最小的作为主服务器。
下图展示在一次故障转移操作中,领头Sentinel向选中的从服务器server3发送SLAVEOF no one命令。
在发送SLAVEOF no one命令之后,领头Sentinel会以每秒一次的频率(平时是每10秒一次),向被升级的从服服务发送INFO命令,并观察命令回复中角色(role)信息,当被升级的从服务器的role由原来的slave变为master时,领头Sentinel就知道被选中的从服务器顺利升级为主服务器了。
(2) 修改从服务器的复制目标
当新的主服务器出现之后,领头Sentinel让已下线主服务器属下的所有从服务器去复制新的主服务器,这一动作可以通过向从服务器发送SLAVE OF命令来实现。
(3) 将旧的主服务器变为从服务器
故障转移操作最后要做的是, 将巳下线的主服务器设置为新的主服务器的从服务器。
当serverl1重新上线时,Sentinel就会向它发送SLAVEOF命令,让它成为server3的从服务器。
10 小结
Redis的Sentinel实现主要包含以下几个方面:三个定时任务、主观下线和客观下线检测、领头Sentinel的选举、故障转移。
(1) 定时任务
**每隔10秒,每个Sentinel会向服务器的_sentinel_:hello频道发送INFO命令,并根据命令的回复信息创建或更新相应的实例结构。
**每隔2秒,每个Sentinel通过命令连接向所有被监视的主从服务器发送自己当前的信息以及对主服务器的认知信息。同时每个Sentinel也会订阅该频道,来了解其他Sentinel节点以及它们对主服务器的认知。
**每隔1秒,每隔Sentinel会向主服务器、从服务器、其余Sentinel发送一条PING命令,并根据回复信息来确认它们是否在线,如果在规定的时间内(通过参数down-after-milliseconds控制)没有进行有效的回复(除了PONG、-LOADING、-MASTERDOWN之外的回复,或者不回复),Sentinel会判定该实例已经进入主观下线。
(2) 主观下线和客观下线检测
客观下线:当Sentinel将一个主服务器判断为主观下线后,为了确认这个主服务是否真的下线了,他会向同样监视这个主服务器的所有其他Sentinel进行询问,看它们是否也认为主服务器是否进入下线状态,如果有足够多数量的Sentinel认为主服务器进入下线状态时,Sentinel就会将主服务器判定为客观下线状态。
(3) 领头Sentinel的选举
在主服务器被判定为客观下线后,Sentinel之间会根据一定的规则选出一个领头Sentinel,故障转移的工作就是这个领头Sentinel来完成的。
(4) 故障转移
**在已下线的主服务器属下的所有从服务器中根据一定的规则选出一个从服务器,并将其转换为主服务器。
**让已下线的主服务器属下的所有从服务器改为复制新的主服务器。
**将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器上线后,它就会成为新的主服务器的从服务器。
本文完
注:本文参考《Redis设计与实现》,如发现错误,请指正!