Redis设计与实现 学习笔记 第十六章 Sentinel

时间:2024-11-08 10:58:03

Sentinel(哨岗、哨兵)是Redis的高可用性(high availability)解决方案:由一个或多个Sentinel实例(instance)组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的从服务器,并在被监视的主服务器进入下线状态时,自动将其属下的某个从服务器升级为新主服务器,然后由新主服务器代替已下线的主服务器继续处理命令请求。

图16-1展示了一个Sentinel系统监视服务器的例子:
在这里插入图片描述
上图中:
1.用双环图案表示主服务器server1。

2.用单环图案表示从服务器server2、server3、server4。

3.Sentinel系统监视所有四个服务器。

假设这时,主服务器server1进入下线状态,那么从服务器server2、server3、server4对主服务器的复制操作将被中止,且Sentinel系统会察觉到server1已下线,如图16-2所示:
在这里插入图片描述
当server1的下线时长超过用户设定的下线时长上限时,Sentinel系统就会对server1执行故障转移操作:
1.首先,Sentinel系统会挑选server1属下的一个从服务器,并将这个被选中的从服务器升级为新的主服务器。

2.之后,Sentinel系统会向server1属下的从服务器发送新的复制指令,让它们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕。

3.另外,Sentinel还会继续监视已下线的server1,并在它重新上线时,将它设置为新的主服务器的从服务器。

例如,图16-3展示了Sentinel系统将server2升级为新主服务器,并让server3和server4成为server2的从服务器的过程:
在这里插入图片描述
之后,如果server1重新上线,它将被Sentinel系统降级为server2的从服务器,如图16-4所示:
在这里插入图片描述
16.1 启动并初始化Sentinel

启动一个Sentinel可以使用命令:
在这里插入图片描述
或命令:
在这里插入图片描述
这两个命令的效果完全相同。

当一个Sentinel启动时,它需要执行以下步骤:
1.初始化服务器。

2.将普通Redis服务器使用的代码替换成Sentinel专用代码。

3.初始化Sentinel状态。

4.根据给定的配置文件,初始化Sentinel的监视主服务器列表。

5.创建连向主服务器的网络连接。

16.1.1 初始化服务器

因为Sentinel本质上只是一个运行在特殊模式下的Redis服务器,所以启动Sentinel的第一步,就是初始化一个普通的Redis服务器,其初始化过程类似于第14章中的普通Redis的初始化过程,但因为Sentinel执行的工作与普通Redis服务器不同,所以Sentinel的初始化过程与普通Redis服务器的初始化过程不完全相同。

例如,Sentinel不会载入RDB文件或AOF文件。
在这里插入图片描述
16.1.2 使用Sentinel专用代码

启动Sentinel的第二个步骤就是将一部分普通Redis服务器使用的代码替换成Sentinel专用代码。比如说,普通Redis服务器使用redis.h/REDIS_SERVERPORT常量值作为服务器端口:
在这里插入图片描述
而Sentinel使用sentinel.c/REDIS_SENTINEL_PORT常量作为服务器端口:
在这里插入图片描述
此外,普通Redis服务器使用redis.c/redisCommandTable作为服务器的命令表:
在这里插入图片描述
而Sentinel使用sentinel.c/sentinelcmds作为服务器的命令表,且其中的INFO命令会使用Sentinel模式下的专用实现sentinel.c/sentinelInfoCommand函数,而非普通Redis服务器使用的实现redis.c/infoCommand函数:
在这里插入图片描述
sentinelcmds命令表也解释了为什么在Sentinel模式下,Redis服务器不能执行诸如SET、DBSIZE(获取当前数据库键数量)、EVAL等命令,因为服务器根本没在命令表中载入这些命令。PING、SENTINEL、INFO、SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE、PUNSUBSCRIBE这七个命令就是客户端能对Sentinel执行的全部命令了。

16.1.3 初始化Sentinel状态

在应用了Sentinel的专用代码后,接下来,服务器会初始化一个sentinel.c/sentinelState结构(简称Sentinel状态),这个结构保存了服务器中所有和Sentinel功能有关的状态(服务器的一般状态仍由redis.h/redisServer结构保存):

struct sentinelState {
    // 当前纪元,用于实现故障转移
    uint64_t current_epoch;
    // 保存了所有被这个sentinel监视的主服务器
    // 字典的键是主服务器的名字
    // 字典的值是一个指向sentinelRedisInstance结构的指针
    dict *masters;
    // 是否进入了TILT模式
    int tilt;
    // 目前正在执行的脚本数量
    int running_scripts;
    // 进入TILT模式的时间
    mstime_t tilt_start_time;
    // 最后一次执行时间处理器的时间
    mstime_t previous_time;
    // 一个FIFO队列,包含了所有要执行的用户脚本
    list *scripts_queue;
} sentinel;

16.1.4 初始化Sentinel状态的masters属性

Sentinel状态中的masters字典记录了所有被Sentinel监视的主服务器的相关信息,其中:
1.字典的键是被监视主服务器的名字。

2.字典的值是被监视主服务器对应的sentinel.c/sentinelRedisInstance结构。

每个sentinelRedisInstance结构(后面简称“实例结构”)代表一个被Sentinel监视的Redis服务器实例,这个实例可以是主服务器、从服务器、另一个Sentinel,但masters属性中只保存主服务器的实例结构。

实例结构包含的属性非常多,以下代码展示了实例结构在表示主服务器时使用的一部分属性:

typedef struct sentinelRedisInstance {
    // 标识值,记录了实例的类型,以及该实例的当前状态
    int flags;
    // 实例的名字
    // 主服务器的名字由用户在配置文件中设置
    // 从服务器以及Sentinel的名字由Sentinel自动设置
    // 格式为ip:port,例如“127.0.0.1:26379”
    char *name;
    // 实例的运行ID
    char *runid;
    // 配置纪元,用于实现故障转移
    uint64_t config_epoch;
    // 实例的地址
    sentinelAddr *addr;
    // SENTINEL down-after-milliseconds选项设定的值
    // 实例无响应多少毫秒后才会被判断为主观下线(subjectively down,即Sentinel主观地认为实例下线了)
    mstime_t down_after_period;
    // SENTINEL monitor <master-name> <IP> <port> <quorum>选项中的quorum参数
    // 判断这个实例为客观下线(objectively down)所需的支持投票数量
    int quorum;
    // SENTINEL parallel-syncs <master-name> <number>选项的值
    // 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
    int parallel_syncs;
    // SENTINEL failover-timeout <master-name> <ms>选项的值
    // 故障迁移时间超出此值时,Sentinel将放弃这次尝试
    mstime_t failover_timeout;
    // ...
} sentinelRedisInstance;

sentinelRedisInstance.addr属性是一个指向sentinel.c/sentinelAddr结构的指针:

typedef struct sentinelAddr {
    char *ip;
    int port;
} sentinelAddr;

对Sentinel状态的初始化将引发对masters字典的初始化,而masters字典的初始化是根据被载入的Sentinel配置文件来进行的。

例如,用户在启动Sentinel时,指定了包含以下内容的配置文件:
在这里插入图片描述
那么Sentinel将为主服务器master1创建如图16-5所示的实例结构:
在这里插入图片描述
并且Sentinel会为主服务器master2创建如图16-6所示的实例结构:
在这里插入图片描述
而这两个实例会被保存到Sentinel状态的masters字典中,如图16-7所示:
在这里插入图片描述
16.1.5 创建连向主服务器的网络连接

初始化Sentinel的最后一步是创建连向被监视主服务器的网络连接,Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并获取命令回复。

对于每个对Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接:
1.一个是命令连接,专门用于向主服务器发送命令,并接收命令回复。

2.另一个是订阅连接,专门用于订阅主服务器的__sentinel__:hello频道。

图16-8展示了一个Sentinel向被它监视的两个主服务器master1和master2创建命令连接和订阅连接的例子:
在这里插入图片描述
16.2 获取主服务器信息

Sentinel默认以十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过INFO命令的回复来获取主服务器的当前信息。

例如,假设主服务器master有三个从服务器slave0、slave1、slave2,且一个Sentinel与主服务器建立了连接,那么Sentinel将持续向主服务器发送INFO命令,并得到类似以下内容的回复:
在这里插入图片描述
通过分析主服务器返回的INFO命令回复,Sentinel可获取以下两方面信息:
1.一方面是关于主服务器本身的信息,包括run_id域记录的服务器运行ID、role域记录的服务器角色。

2.另一方面是关于主服务器属下的从服务器的信息,每个从服务器都由一个“slave”字符串开头的行记录,每行的ip域记录了从服务器的IP、port域记录了从服务器的端口。根据IP和端口,Sentinel无须用户提供从服务器的地址信息,就可以自动发现从服务器。

根据run_id和role域记录的信息,Sentinel将对主服务器的实例结构进行更新,例如,主服务器重启后,它的运行ID就会变化,Sentinel检测到这一情况后,就会对实例结构的运行ID进行更新。

主服务器返回的从服务器信息会用于更新主服务器实例结构的slaves字典,这个字典记录了主服务器属下从服务器的名单:
1.字典的键是Sentinel自动设置的从服务器名,格式为ip:port。

2.字典的值是从服务器对应的实例结构。

Sentinel在分析INFO命令中包含的从服务器信息时,会检查从服务器对应的实例结构是否已经存在于slaves字典:
1.如果从服务器对应的实例结构已存在,那么Sentinel将对其进行更新。

2.如果从服务器对应的实例结构不存在,那么Sentinel将为这个从服务器创建一个实例结构。

对于上图的例子,Sentinel将分别为三个从服务器创建它们各自的结构,并将这些结构保存到主服务器实例结构的slaves字典里,如图16-10所示:
在这里插入图片描述
注意上图中主从服务器实例结构之间的区别:
1.主服务器实例结构的flags属性值为SRI_MASTER,而从服务器的是SRI_SLAVE。

2.主服务器实例结构的name属性是用户使用Sentinel配置文件设置的,而从服务器的是ip:port。

16.3 获取从服务器信息

当Sentinel发现主服务器有新从服务器出现时,Sentinel除了会为这个从服务器创建相应的实例结构,还会创建到从服务器的命令连接和订阅连接,如图16-10所示:
在这里插入图片描述
在创建命令连接后,Sentinel默认每十秒一次通过命令连接向从服务器发送INFO命令,并获得类似以下内容的回复:
在这里插入图片描述
根据INFO命令的回复,Sentinel会提取出以下信息:
1.从服务器的运行ID run_id。

2.从服务器的角色role。

3.主服务器的IP地址master_host和端口号master_port。

4.主从服务器的连接状态master_link_status。

5.从服务器的优先级slave_priority。

6.从服务器的复制偏移量slave_repl_offset。

根据这些信息,Sentinel会对从服务器的实例结构进行更新,图16-12展示了Sentinel根据上面的INFO命令回复对从服务器的实例更新之后的结果:
在这里插入图片描述
16.4 向主服务器和从服务器发送信息

默认Sentinel会以两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:
在这里插入图片描述
这条命令向服务器的__sentinel__:hello频道发送了一条信息,信息的内容由以下参数组成:
1.以s_开头的参数记录的是Sentinel本身的信息,参数的意义见表16-2:
在这里插入图片描述
2.以m_开头的参数记录的是主服务器的信息,各个参数的意义见表16-3:
在这里插入图片描述
16.5 接收来自主服务器和从服务器的频道信息

当Sentinel与一个主服务器或从服务器建立起订阅连接后,Sentinel会通过订阅连接,向服务器发送以下命令:
在这里插入图片描述
Sentinel对__sentinel__:hello频道的订阅会一直持续到Sentinel与服务器的连接断开为止。

即,对每个与Sentinel连接的服务器,Sentinel既通过命令连接向服务器的__sentinel__:hello频道发送信息,又通过订阅连接接收服务器的__sentinel__:hello频道的信息,如图16-13所示:
在这里插入图片描述
对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他Sentinel接收到,这些信息被用于更新其他Sentinel对发送信息的Sentinel的认知,也被用于更新其他Sentinel对被监视服务器的认知。

例如,现在有sentinel1、sentinel2、sentinel3三个Sentinel在监视同一服务器,当sentinel1向服务器的__sentinel__:hello频道发送一条信息时,所有订阅了__sentinel__:hello频道的Sentinel(包括sentinel1自己在内)都会收到这条信息,如图16-14所示:
在这里插入图片描述
当一个Sentinel从__sentinel__:hello频道收到一条信息时,Sentinel会对这条信息进行分析,提取出信息中的Sentinel IP地址、Sentinel端口、Sentinel运行ID等八个参数,并进行以下检查:
1.如果信息中的Sentinel运行ID和自身的相同,说明这条信息是自己发出的,Sentinel将丢弃这条信息,不做处理。

2.如果信息中的Sentinel运行ID和自身的不同,说明这条信息是监视同一个服务器的其他Sentinel发来的,接收信息的Sentinel将根据信息中的参数,对相应主服务器的实例结构进行更新。

16.5.1 更新sentinels字典

Sentinel为主服务器创建的实例结构sentinelRedisInstance中有一个sentinels字典,其中除了Sentinel本身的实例结构外,还有所有监视这个主服务器的Sentinel的实例结构:
1.sentinels字典的键是Sentinel的名字,格式为ip:port。

2.sentinels字典的值是所对应的Sentinel的实例结构。

当一个目标Sentinel收到来自其他源Sentinel发来的信息时,目标Sentinel会从信息中获取以下两方面参数:
1.与Sentinel有关的参数:源Sentinel的IP、端口、运行ID、配置纪元。

2.与主服务器有关的参数:源Sentinel正在监视的主服务器的名字、IP、端口号、配置纪元。

根据信息中提取出的主服务器参数,目标Sentinel会在自己的Sentinel状态的masters字典中查找相应的主服务器实例结构,然后根据提取出的Sentinel参数,检查主服务器实例结构的sentinuels字典中,源Sentinel的实例结构是否存在:
1.如果源Sentinel实例结构已存在,则更新它。

2.如果源Sentinel实例结构不存在,那么说明这个Sentinel是刚开始监视主服务器的新Sentinel,此时会为这个新Sentinel创建实例结构并将其添加到sentinels字典里。

例如,有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将执行以下操作:
1.第一条信息的发送者为127.0.0.1:26379,是自己发送的,这条信息会被忽略。

2.第二条信息的发送者为127.0.0.1:26381,Sentinel会根据这条信息对sentinels字典中该发送者对应的实例结构进行更新。

3.第三条信息的发送者为127.0.0.1:26380,Sentinel会根据这条信息对sentinels字典中该发送者对应的实例结构进行更新。

图16-15展示了Sentinel 127.0.0.1:26379为主服务器127.0.0.1:6379创建的实例结构,以及其中的sentinels字典:
在这里插入图片描述
其他两个Sentinel也会创建类似上图的sentinels字典,区别在于其中只保存了除自己外的其他正在监视该主服务器的Sentinel。

因为一个Sentinel可通过分析接收到的频道信息来获知其他Sentinel的存在,并通过发送频道信息来让其他Sentinel知道自己的存在,所以用户使用Sentinel时不需要提供各个Sentinel的地址信息,监视同一个主服务器的多个Sentinel可以自动发现对方。

16.5.2 创建连向其他Sentinel的命令连接

当Sentinel通过频道信息发现一个新Sentinel时,它不仅会为新Sentinel在sentinels字典中创建相应的实例结构,还会创建一个连向新Sentinel的命令连接,而新Sentinel也同样会创建连向这个Sentinel的命令连接:
在这里插入图片描述
使用命令连接相连的各个Sentinel可向其他Sentinel发送命令请求来进行信息交换。

Sentinel之间不会创建订阅连接,Sentinel在连接主服务器或从服务器时创建订阅连接是为了通过频道发来的信息来发现新Sentinel,所以才需要建立订阅连接,而Sentinel之间没有建立订阅连接的需要。

16.6 检测主观下线状态

默认Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他Sentinel)发送PING命令,并通过PING命令的回复来判断实例是否在线。

在图16-17展示的例子中,带箭头的连线表示发送PING命令:
在这里插入图片描述
实例对PING命令的回复可分为:
1.有效回复:+PONG、-LOADING、-MASTERDOWN三种。

2.无效回复:除有效回复外的其他回复,或指定时限内没有返回的回复。

Sentinel配置文件中的down-after-milliseconds选项指定了Sentinel判断实例进入主观下线所需的毫秒数:如果一个实例在指定毫秒内,连续向Sentinel返回无效回复,那么Sentinel会修改这个实例对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,以此来表示实例进入了主观下线状态,例如:
在这里插入图片描述
down-after-milliseconds会被Sentinel用来判断主服务器、从服务器、其他Sentinel的主观下线状态。

对于监视同一个主服务器的多个Sentinel来说,这些Sentinel设置的down-after-milliseconds选项值可能不同,因此,当一个Sentinel将主服务器判断为主观下线时,其他Sentinel可能仍认为主服务器处于在线状态。

16.7 检查客观下线状态

当Sentinel将一个主服务器判断为主观下线后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入下线状态(指主观下线或客观下线)。当Sentinel从其他Sentinel接收到足够数量的已下线判断后,Sentinel会将从服务器判定为客观下线,并对主服务器进行故障转移操作。

16.7.1 发送SENTINEL is-master-down-by-addr命令

Sentinel使用:
在这里插入图片描述
命令询问其他Sentinel是否同意主服务器已下线,命令参数的意义见表16-4:
在这里插入图片描述
例如,被Sentinel判断为主观下线的主服务器IP为127.0.0.1,端口号为6379,且Sentinel当前配置纪元为0,那么Sentinel将向其他Sentinel发送以下命令来询问主服务器的下线状态:
在这里插入图片描述
16.7.2 接收SENTINEL is-master-down-by-addr命令

当一个目标Sentinel接收到另一个源Sentinel发来的SENTINEL is-master-down-by命令时,目标Sentinel会检查要查询的主服务器是否已下线,然后向源Sentinel返回一条包含以下三个参数的Multi Bulk(可用于包含多个值的请求或回复,如MSET命令获取多个键的值时使用)作为回复:
1.<down_state>

2.<leader_runid>

3.<leader_epoch>

表16-5记录了这三个参数的意义:
在这里插入图片描述
16.7.3 接收SENTINEL is-master-down-by-addr命令的回复

根据其他Sentinel发回的SENTINEL is-master-down-byaddr命令回复,Sentinel将统计同意主服务器已下线的Sentinel数量,当达到配置指定的判断客观下线所需的数量时,Sentinel会将主服务器实例结构flags属性的SRI_O_DOWN表示打开,表示主服务器已进入客观下线状态:
在这里插入图片描述
例如,Sentinel在启动时载入了以下配置:
在这里插入图片描述
那么包括当前master在内,只要总共有两个Sentinel认为主服务器已进入下线状态,那么当前Sentinel就将主服务器判断为客观下线。

对于监视同一个主服务器的多个Sentinel来说,它们将主服务器判断为客观下线的条件可能不同。

16.8 选举领头Sentinel

当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器执行故障转移操作。

以下是Redis选举领头Sentinel的规则和方法:
1.所有在线的Sentinel都有被选为领头Sentinel的资格,即,监视同一个主服务器的所有在线Sentinel都有可能成为领头Sentinel。

2.每次进行领头Sentinel选举后,不论选举是否成功,所有Sentinel的配置纪元(configuration epoch)都会自增。配置纪元实际就是一个计数器。

3.在一个配置纪元里,所有Sentinel都有一次将某个Sentinel设为局部领头Sentinel的机会,且一旦局部领头Sentinel被设置,在这个配置纪元里就不能再更改。

4.每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设为局部领头Sentinel。

5.当一个源Sentinel向另一个目标Sentinel发送SENTINEL is-master-down-by-addr命令,且命令中的runid参数不是*号而是源Sentinel的运行ID时,这表示源Sentinel要求目标Sentinel将前者设为后者的局部领头Sentinel。

6.Sentinel设置局部领头Sentinel的规则是先到先得:最先向目标Sentinel发送设置要求的原Sentinel将成为目标Sentinel的领头Sentinel,之后接收到的所有设置要求都会被目标Sentinel拒绝。

7.目标Sentinel接收到SENTINEL is-master-down-by-addr命令后,将向源Sentinel返回一条命令回复,回复中的leader_runid和leader_epoch参数分别记录了目标Sentinel的局部领头Sentinel的运行ID和配置纪元。

8.源Sentinel接收到目标Sentinel返回的命令回复后,会检查回复中leader_epoch参数的值和自己的配置纪元是否相同,如果相同,那么源Sentinel继续取出回复中的leader_runid参数,如果leader_runid参数的值和源Sentinel的运行ID一致,那么表示目标Sentinel将源Sentinel设置成了局部领头Sentinel。

9.如果有某个Sentinel被半数以上Sentinel设置成了局部领头Sentinel,那么这个Sentinel成为领头Sentinel。例如,在一个由10个Sentinel组成的Sentinel系统里,只要有大于等于6个Sentinel将某个Sentinel设置为局部领头Sentinel,那么被设置的那个Sentinel就会成为领头Sentinel。

10.因为领头Sentinel的产生需要半数以上Sentinel的支持,并且每个Sentinel在每个配置纪元里只能设置一次局部Sentinel,所以在一个配置纪元里,只会出现一个领头Sentinel。

11.如果给定时限内没有一个Sentinel被选举为领头Sentinel,那么各个Sentinel将在一段时间后再次进行选举,直到选出领头Sentinel为止。

为了熟悉以上规则,我们来看一个选举领头Sentinel的过程。

假设有三个Sentinel正在监视同一主服务器,且这三个Sentinel之前已经通过SENTINEL is-master-down-by-addr命令确认主服务器进入了客观下线状态,如图16-20所示:
在这里插入图片描述
那么为了选出领头Sentinel,三个Sentinel将再次向其他Sentinel发送SENTINEL is-master-down-by-addr命令,如图16-21所示:
在这里插入图片描述
和检测客观下线状态时发送的SENTINEL is-master-down-by-addr命令不同,Sentinel这次发送的命令会带有Sentinel自己的运行ID,例如:
在这里插入图片描述
如果接受到上图所示命令的Sentinel还没有设置局部领头Sentinel,它就会将发送该命令的Sentinel设置为自己的局部领头Sentinel,并返回类似以下的命令回复:
在这里插入图片描述
然后接收到命令回复的Sentinel就可根据这一回复,统计出有多少Sentinel将自己设置成了局部领头Sentinel。

根据命令请求发送的先后顺序不同,可能会有某个Sentinel的SENTINEL is-master-down-by-addr命令比其他Sentinel发送的相同命令更快到达,并最终胜出领头Sentinel的选举,然后这个领头Sentinel就可以开始对主服务器执行故障转移操作了。

有个疑问,如果有4个Sentinel,1和2是互相的局部领头Sentinel,3和4是互相的局部领头Sentinel,且由于网络原因(1、2在一起,3、4在一起,且1、2和3、4离得很远),每次执行选举都是这个结果,岂不是永远不会出现领头Sentinel了?因为没有一个Sentinel是3个Sentinel的局部领头Sentinel。

16.9 故障转移

在选举产生出领头Sentinel后,领头Sentinel将对已下线的主服务器执行故障转移操作,该操作包含三个步骤:
1.在已下线主服务器属下的所有从服务器里,挑一个将其转换为主服务器。

2.让已下线的主服务器属下的所有从服务器改为复制新的主服务器。

3.让已下线的主服务器设为新主服务器的从服务器,当这个旧的主服务器重新上线时,它会成为新的主服务器的从服务器。

16.9.1 选出新的主服务器

第一步会挑选一个状态良好、数据完整的从服务器,然后向其发送SLAVEOF no one命令,将其转换为主服务器。

新的主服务器是怎样选出来的?领头Sentinel会将已下线主服务器的所有从服务器保存到一个列表里,然后按以下规则对列表进行过滤:
1.删除列表中所有处于下线或断线状态的从服务器。

2.删除列表中所有近5秒没有回复过领头Sentinel的INFO命令的从服务器。

3.删除所有与已下线主服务器连接断开超过10*down-after-milliseconds毫秒数的从服务器,这可以保证从服务器的数据是比较新的。

之后,领头Sentinel根据从服务器的优先级,对列表中剩余的从服务器进行排序,并选出优先级最高的从服务器。

如果有多个并列最高优先级的从服务器,那么领头Sentinel会选择复制偏移量最大的从服务器。

如果有多个并列最高优先级、并列最大复制偏移量的从服务器,那么领头Sentinel会选择运行ID最小的从服务器。

图16-22展示了在一次故障转移操作中,领头Sentinel向被选中的从服务器发送SLAVEOF no one命令的情形:
在这里插入图片描述
在发送SLAVEOF no one命令后,领头Sentinel会以每秒一次的频率(平时是十秒一次),向被升级的从服务器发送INFO命令,并观察命令回复中的角色(role)信息,当其从slave变为master时,领头Sentinel就知道升级成功了。

例如,在图16-22展示的例子中,领头Sentinel会一直向server2发送INFO命令,当server2返回的命令从:
在这里插入图片描述
变为:
在这里插入图片描述
领头Sentinel就知道server2已经成功升级为主服务器了。

图16-23展示了server2升级成功后,各个服务器和领头Sentinel的样子:
在这里插入图片描述
16.9.2 修改从服务器的复制目标

当出现新主服务器后,领头Sentinel下一步要做的就是,让已下线主服务器属下的所有从服务器去复制新的主服务器,这一动作可通过向从服务器发送SLAVEOF命令来实现。
在这里插入图片描述
在这里插入图片描述
16.9.3 将旧的主服务器变为从服务器

因为旧的主服务器server1已下线,所以将server1设为从服务器的设置只是修改Sentinel中server1对应的实例结构,当server1重新上线时,Sentinel就会向它发送SLAVEOF命令,让它成为server2的从服务器。

16.10 重点回顾

1.Sentinel只是一个运行在特殊模式下的Redis服务器,它使用了和普通模式不同的命令表,所以Sentinel模式能够使用的命令和普通Redis服务器能够使用的命令不同。

2.Sentinel会读入用户指定的配置文件,为每个要被监视的主服务器创建相应的实例结构,并创建连向主服务器的命令连接和订阅连接,其中命令连接用于向主服务器发送命令请求,而订阅连接用于接收指定频道的消息。

3.Sentinel通过向主服务器发送INFO命令来获得主服务器属下所有从服务器的地址信息,并为这些从服务器创建相应的实例结构,以及连向这些从服务器的命令连接和订阅连接。

4.一般情况下,Sentinel每十秒一次向被监视的主服务器和从服务器发送INFO命令,当主服务器处于下线状态,或Sentinel正在对主服务器进行故障转移操作时,Sentinel向从服务器发送INFO命令的频率会改为每秒一次。

5.对于监视同一个主服务器和从服务器的多个Sentinel来说,它们会以每两秒一次的频率,通过向被监视服务器的__sentinel__:hello频道发送消息来向其他Sentinel宣告自己的存在。

6.每个Sentinel也会从__sentinel__:hello频道中接收其他Sentinel发来的信息,并根据这些信息为其他Sentinel创建相应的实例结构,以及命令连接。

7.Sentinel只会与主服务器和从服务器创建命令连接和订阅连接,Sentinel与Sentinel之间只创建命令连接。

8.Sentinel以每秒一次的频率向实例(主服务器、从服务器、其他Sentinel)发送PING命令,并根据实例对PING命令的回复来判断实例是否在线,当一个实例在指定时长中连续向Sentinel发送无效回复时,Sentinel会将这个实例判断为主观下线。

9.当Sentinel将一个主服务器判断为主观下线时,它会向同样监视这个主服务器的其他Sentinel进行询问,看它们是否同意这个主服务器已进入下线状态。

10.当Sentinel收集到足够多的主观下线投票后,它会将主服务器判断为客观下线,并发起一次针对主服务器的故障转移操作。

16.11 参考资料

Sentinel系统选举领头Sentinel的方法是对Raft算法的领头选举方法的实现,关于这一方法的详细信息可参考Raft算法的论文。