redis incrby,《Reids 设计与实现》第十三章 Sentinel

 2023-09-25 阅读 27 评论 0

摘要:《Reids 设计与实现》第十三章 Sentinel 文章目录《Reids 设计与实现》第十三章 Sentinel一、简介二、启动并初始化 Sentinel1.初始化服务器2.使用 Sentinel 专用代码3.初始化 Sentinel 状态4.初始化 Sentinel 状态的 masters 属性5.创建连向主服务器的网络连接三、获取主服务

《Reids 设计与实现》第十三章 Sentinel

文章目录

  • 《Reids 设计与实现》第十三章 Sentinel
  • 一、简介
  • 二、启动并初始化 Sentinel
    • 1.初始化服务器
    • 2.使用 Sentinel 专用代码
    • 3.初始化 Sentinel 状态
    • 4.初始化 Sentinel 状态的 masters 属性
    • 5.创建连向主服务器的网络连接
  • 三、获取主服务器信息
  • 四、获取从服务器信息
  • 五、向主服务器和从服务器发送信息
  • 六、接收来自主服务器和从服务器的频道信息
    • 1.更新 sentinels 字典
    • 2.创建连向其他 Sentinel 的命令连接
  • 七、检测主观下线状态
  • 八、检测客观下线状态
    • 1.发送 SENTINEL is-master-down-by-addr 命令
    • 2.接收 SENTINEL is-master-down-by-addr 命令
    • 3.接收 SENTINEL is-master-down-by-addr 命令的回复
  • 九、选举领头 Sentinel
  • 十、故障转移
    • 1.选出新的主服务器
    • 2.修改从服务器的复制目标
    • 3.将旧的主服务器变为从服务器
  • 十一、重点回顾

一、简介

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

在这里插入图片描述

redis incrby?图 16-1 展示了一个 Sentinel 系统监视服务器的例子,其中:

  • 用双环图案表示的是当前的主服务器 server1
  • 用单环图案表示的是主服务器的三个从服务器 server2、server3 以及 server4
  • server2、server3、server4 三个服务器正在复制主服务器 server1,而 Sentinel 系统则在监视所有四个服务器

假设这时,主服务器 server1 进入下线状态,那么从服务器 server2、server3、server4 对主服务器的复制操作将被中止,并且 Sentinel 系统会察觉到 server1 已下线,如图 16-2 所示(下线的服务器用虚线表示)

在这里插入图片描述

当 server1 的下线时长超过用户设定的下线时长上限时,Sentinel 系统就会对 server1 执行故障转移操作:

  • 首先,Sentinel 系统会挑选 server1 属下的其中一个从服务器,并将这个被选中的从服务器升级为新的主服务器
  • 之后,Sentinel 系统会向 server1 属下的所有从服务器发送新的复制指令,让它们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕
  • 另外,Sentinel 还会继续监视已下线的 server1,并在它重新上线时,将它设置为新的主服务器的从服务器

图 16-3 展示了 S恩听饿了系统将 server2 升级为新的主服务器,并让服务器 server3 和 server4 成为 server2 的从服务器的过程

redis cluster原理,在这里插入图片描述

之后,如果 server1 重新上线的话,它将被 Sentinel 系统降级为 server2 的从服务器,如图 16-4 所示

在这里插入图片描述

二、启动并初始化 Sentinel

启动一个 Sentinel 可以使用命令:

$ redis-sentinel sentinel.conf

或者命令:

$ redis-server sentinel.conf --sentinel

redis docker,这两个命令的效果完全相同

当一个 Sentinel 启动时,它需要执行以下步骤:

  1. 初始化服务器
  2. 将普通 Redis 服务器使用的代码替换成 Sentinel 专用代码
  3. 初始化 Sentinel 状态
  4. 根据给定的配置文件,初始化 Sentinel 的监视主服务器列表
  5. 创建连向主服务器的网络连接

1.初始化服务器

首先,因为 Sentinel 本质上只是一个运行在特殊模式下的 Redis 服务器,所以启动 Sentinel 的第一步,就是初始化一个普通的 Redis 服务器

不过,因为 Sentinel 执行的工作和普通 Redis 服务器执行的工作不同,所以 Sentinel 的初始化过程和普通 Redis 服务器的初始化过程并不完全相同

例如,普通服务器在初始化时会通过载入 RDB 文件或者 AOF 文件来还原数据库状态,但是因为 Sentinel 并不使用数据库,所以初始化 Sentinel 时就不会载入 RDB 文件或者 AOF 文件

redis 集群。表 16-1 展示了 Redis 服务器在 Sentinel 模式下运行时,服务器各个主要功能的使用情况

在这里插入图片描述

2.使用 Sentinel 专用代码

启动 Sentinel 的第二个步骤就是将一部分普通 Redis 服务器使用的代码替换成 Sentinel 专用代码。比如说,普通 Redis 服务器使用 redis.h/REDIS_SERVERPORT 常量的值作为服务器端口:

#define REDIS_SERVERPORT 6379

而 Sentinel 则使用 sentinel.c/REDIS_SENTINEL_PORT 常量的值作为服务器端口:

#define REDIS_SENTINEL_PORT 26379

除此之外,普通 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、PSUBSCRIBE 和 PUNSUBSCRIBE 这七个命令就是客户端可以对 Sentinel 执行的全部命令了

3.初始化 Sentinel 状态

redis基础?在应用了 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;

4.初始化 Sentinel 状态的 masters 属性

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

  • 字典的键是被监视主服务器的名字
  • 而字典的值则是被监视主服务器对应的 sentinel.c/sentinelRedisInstance 结构

每个 sentinelRedisInstance 结构(后面简称 “实例结构”)代表一个被 Sentinel 监视的 Redis 服务器实例(instance),这个实例可以是主服务器、从服务器,或者另外一个 Sentinel

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

typedef struct sentinelRedisInstance{//标识值,记录了实例的类型,以及该实例的当前状态int flags;//实例的名字//主服务器的名字由用户在配置文件中设置//从服务器以及 Sentinel 的名字由 Sentinel 自动设置//格式为 ip:port,例如 "127.0.0.1:16379"char *name;//实例的运行 IDchar *runid;//配置纪元,用于实现故障转移uint64_t config_epoch;//实例的地址sentinelAddr *addr;//SENTINEL down-after-milliseconds 选项设定的值//实例无响应多少毫秒之后才会被判断为主观下线(subjectively down)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> 选项的值//刷新故障迁移状态的最大时限mstime_t failover_timeout;//...
}sentinelRedisInstance; 

sentinelRedisInstance.addr 属性是一个指向 sentinel.c/sentinelAddr 结构的指针,这个结构保存着实例的 IP 地址和端口号:

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

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

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

redis持久化、初始化 Sentinel 的最后一步是创建连向被监视主服务器的网络连接,Sentinel 将成为主服务器的客户端,它可以向服务器发送命令,并从命令回复中获取相关的信息

对于每个被 Sentinel 监视的主服务器来说,Sentinel 会创建两个连向主服务器的异步网络连接:

  • 一个是命令连接,这个连接专门用于向主服务器发送命令,并接收命令回复
  • 另一个是订阅连接,这个连接专门用于订阅主服务器的 __sentinel__:hello 频道

为什么有两个连接?

在 Redis 2.8 的发布与订阅功能中,被发送的信息都不会保存在 Redis 服务器里面,如果在信息发送时,想要接收信息的客户端不在线或者断线,那么这个客户端就会丢失这条信息。因此,为了不丢失 __sentinel__:hello 频道的任何信息,Sentinel 必须专门用一个订阅连接来接收该频道的信息

另一方面,除了订阅频道之外,Sentinel 还必须向主服务器发送命令,以此来与主服务器进行通信,所以 Sentinel 还必须向主服务器创建命令连接

redis数据库设计?此外,因为 Sentinel 需要与多个实例创建多个网络连接,所以 Sentinel 使用的是异步连接

图 16-8 展示了一个 Sentinel 向被它监视的两个主服务器 master1 和 master2 创建命令连接和订阅连接的例子

在这里插入图片描述

三、获取主服务器信息

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

举个例子,假设如图 16-9 所示,主服务器 master 有三个从服务器 slave0、slave1 和 slave2,并且一个 Sentinel 正在连接主服务器,那么 Sentinel 将持续地向主服务器发送 INFO 命令,并获得类似于以下内容的回复:

# Server
...
run_id:7611c59dc3a29aa6fa0609f841bb6a1019008a9c
...
# Replication
role:master
...
slave0:ip=127.0.0.1,port=11111,state=online,offset=43,lag=0
slave1:ip=127.0.0.1,port=22222,state=online,offset=43,lag=0
slave2:ip=127.0.0.1,port=33333,state=online,offset=43,lag=0
...
# Other sections

redis,通过分析主服务器返回的 INFO 命令回复,Sentinel 可以获取以下两方面的信息:

  • 一方面是关于主服务器本身地信息,包括 run_id 域记录的服务器运行 ID,以及 role 域记录的服务器角色
  • 另一方面是关于主服务器属下所有从服务器地信息,每个从服务器都由一个 “slave” 字符串开头的行记录,每行的 ip= 域记录了从服务器的 IP 地址,而 port= 域则记录了从服务器的端口号。根据这些 IP 地址和端口号,Sentinel 无须用户提供从服务器的地址信息,就可以自动发现从服务器

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

至于主服务器返回的从服务器信息,则会被用于更新主服务器实例结构的 slaves 字典,这个字典记录了主服务器属下从服务器的名单:

  • 字典的键是由 Sentienl 自动设置的从服务器名字,格式为 ip:port: 如对于 IP 地址为 127.0.0.1,端口号为 11111 的从服务器来说,Sentinel 为它设置的名字就是 127.0.0.1:11111
  • 至于字典的值则是从服务器对应的实例结构:比如说,如果键是 127.0.0.1:11111,那么这个键的值就是 IP 地址为 127.0.0.1,端口号为 11111 的从服务器的实例结构

Sentinel 在分析 INFO 命令中包含的从服务器信息时,会检查从服务器对应的实例结构是否已经存在于 slaves 字典:

  • 如果从服务器对应的实例结构已经存在,那么 Sentinel 对从服务器的实例结构进行更新
  • 如果从服务器对应的实例结构不存在,那么说明这个从服务器是新发现的从服务器,Sentinel 会在 slaves 字典中为这个从服务器新建一个实例结构

对于我们之前列举的主服务器 master 和三个从服务器 slave0、slave1 和 slave2 的例子来说,Sentinel 将分别为三个从服务器创建它们各自的实例结构,并将这些结构保存到主服务器实例结构的 slaves 字典里面,如图 16-10 所示

k8s redis集群,在这里插入图片描述

注意对比图中主服务器实例结构和从服务器实例结构之间的区别:

  • 主服务器实例结构的 flags 属性的值为 SRI_MASTER,而从服务器实例结构的 flags 属性的值为 SRI_SLAVE
  • 主服务器实例结构的 name 属性的值是用户使用 Sentinel 配置文件设置的,而从服务器实例结构的 name 属性的值则是 Sentinel 根据从服务器的 IP 地址和端口号自动设置的

四、获取从服务器信息

当 Sentinel 发现主服务器有新的从服务器出现时,Sentinel 除了会为这个新的从服务器创建相应的实例结构之外,Sentinel 还会创建连接到从服务器的命令连接和订阅连接

举个例子,对于图 16-10 所示的主从服务器关系来说,Sentinel 将对 slave0、slave1 和 slave2 三个从服务器分别创建命令连接和订阅连接,如图 16-11 所示

在这里插入图片描述

redis sentinel集群?在创建命令之后,Sentinel 在默认情况下,会以每十秒一次的频率通过命令连接向从服务器发送 INFO 命令,并获得类似于以下内容的回复:
在这里插入图片描述
根据 INFO 命令的回复,Sentinel 会提取出以下信息:

  • 从服务器的运行 ID run_id
  • 从服务器的角色 role
  • 主服务器的 IP 地址 master_host,以及主服务器的端口号 master_port
  • 主从服务器的连接状态 master_link_status
  • 从服务器的优先级 slave_repl_offset

根据这些信息,Sentinel 会对从服务器的实例结构进行更新,图 16-12 展示了 Sentinel 根据上面的 INFO 命令回复对从服务器的实例结构进行更新之后,实例结构的样子:

在这里插入图片描述

五、向主服务器和从服务器发送信息

在默认情况下,Sentinel 会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:

PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"

这条命令向服务器的 __sentinel__:hello 频道发送了一条信息,信息的内容由多个参数组成:

  • 其中以 s_ 开头的参数记录的是 Sentinel 本身的信息,各个 参数的意义如表 16-2 所示
  • 而 m_ 开头的参数记录的则是主服务器的信息,各个参数的意义如表 16-3 所示。如果 Sentinel 正在监视的是主服务器,那么这些参数记录的就是主服务器的信息;如果 Sentinel 正在监视的是从服务器,那么这些参数记录的就是从服务器正在复制的主服务器信息

在这里插入图片描述
在这里插入图片描述

以下是一条 Sentinel 通过 PUBLISH 命令向主服务器发送的信息示例:

"127.0.0.1,26379,e955b4c85598ef5b5f055bc7ebf5e828dbed4fa,0,mymaster,127.0.0.1,6379,0"

这个示例包含了以下信息:

  • Sentinel 的 IP 地址为 127.0.0.1 端口号为 16379,运行 ID 为 26379,e955b4c85598ef5b5f055bc7ebf5e828dbed4fa,当前的配置纪元为 0
  • 主服务器的名字为 mymaster,IP 地址为 127.0.0.1,端口号为 6379,当前的配置纪元为 0

六、接收来自主服务器和从服务器的频道信息

当 Sentinel 与一个主服务器或者从服务器建立起订阅连接之后,Sentinel 就会通过订阅连接,向服务器发送以下命令:

SUBSCRIBE __sentinel__:hello

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 等八个参数,并进行以下检查:

  • 如果信息中记录的 Sentinel 运行 ID 和接收信息的 Sentinel 的运行 ID 相同,那么说明这条信息是 Sentinel 自己发送的,Sentinel 将丢弃这条信息,不做进一步处理
  • 相反地,如果信息中记录的 Sentinel 运行 ID 和接收信息的 Sentinel 的运行 ID 不相同,那么说明这条信息是监视同一个服务器的其他 Sentinel 发来的,接受信息的 Sentinel 将根据信息中的各个参数,对相应的主服务器的实例结构进行更新

1.更新 sentinels 字典

Sentinel 为主服务器创建的实例结构中的 sentinels 字典保存了除 Sentinel 本身之外,所有同样监视这个主服务器的其他 Sentinel 的资料:

  • sentinels 字典的键是其中一个 Sentinel 的名字,格式为 ip:port,比如对于 IP 地址为 127.0.0.1,端口号为 26379 的 Sentinel 来说,这个 Sentinel 在 sentinels 字典中的键就是 “127.0.0.1:26379”
  • sentinels 字典的值则是键所对应 Sentinel 的实例结构,比如对于键 “127.0.0.1:26379” 来说,这个键在 sentinels 字典中的值就是 IP 为 127.0.0.1,端口号为 26379 的 Sentinel 的实例结构

当一个 Sentinel 接收到其他 Sentinel 发来的信息时(我们称呼发送信息的 Sentinel 为源 Sentinel,接收信息的 Sentinel 为目标 Sentinel),目标 Sentinel 会从信息中分析并提取出以下两方面参数:

  • 与 Sentinel 有关的参数:源 Sentinel 的 IP 地址、端口号、运行 ID 和配置纪元
  • 与主服务器有关的参数:源 Sentinel 正在监视的主服务器的名字、IP 地址、端口号和配置纪元

根据信息中提取出的主服务器参数,目标 Sentinel 会在自己的 Sentinel 状态的 masters 字典中查找相应的主服务器实例结构,然后根据提取出的 Sentinel 参数,检查主服务器实例结构的 sentinels 字典中,源 Sentinel 的实例结构是否存在:

  • 如果源 Sentinel 的实例结构已经存在,那么对源 Sentinel 的实例结构进行更新
  • 如果源 Sentinel 的实例结构不存在,那么说明源 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 接收到以下信息时:
在这里插入图片描述
Sentinel 将执行以下动作:

  • 第一条信息的发送者为 127.0.0.1:26379 自己,这条信息会被忽略
  • 第二条信息的发送者为 127.0.0.1:26381,Sentinel 会根据这条信息中提取出的内容,对 sentinels 字典中 127.0.0.1:26381 所对应的实例结构进行更新
  • 第二条信息的发送者为 127.0.0.1:26380,Sentinel 会根据这条信息中提取出的内容,对 sentinels 字典中 127.0.0.1:26380 所对应的实例结构进行更新

图 16-15 展示了 Sentinel 127.0.0.1:26379 为主服务器 127.0.0.1:6379 创建的实例结构,以及结构中的 sentinels 字典

在这里插入图片描述

和 127.0.0.1:26379 一样,其他两个 Sentinel 也会创建类似于图 16-15 所示的 sentinels 字典,区别在于字典中保存的 Sentinel 信息不同:

  • 127.0.0.1:26380 创建的 sentinels 字典会保存 127.0.0.1:26379 和 127.0.0.1:26381 两个 Sentinel 的信息
  • 127.0.0.1:26381 创建的 sentinels 字典会保存 127.0.0.1:26379 和 127.0.0.1:26380 两个 Sentinel 的信息

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

2.创建连向其他 Sentinel 的命令连接

当 Sentinel 通过频道信息发现一个新的 Sentinel 时,它不仅会为新 Sentinel 在 sentinels 字典中创建相应的实例结构,还会创建一个连向新 Sentinel 的命令连接,而新 Sentinel 也同样会创建连向这个 Sentinel 的命令连接,最终监视同一主服务器的多个 Sentinel 将形成相互连接的网络:Sentinel A 有连向 Sentinel B 的命令连接,而 Sentinel B 也有连向 Sentinel A 的命令连接

图 16-16 展示了三个监视同一主服务器的 Sentinel 之间是如何互相连接的

在这里插入图片描述

使用命令连接相连的各个 Sentinel 可以通过向其他 Sentinel 发送命令请求来进行信息交换

Sentinel 之间不会创建订阅连接

Sentinel 在连接主服务器或者从服务器时,会同时创建命令连接和订阅连接,但是在连接其他 Sentienl 时,却只会创建命令连接,而不创建订阅连接。这是因为 Sentinel 需要通过接收主服务器或者从服务器发来的频道信息来发现未知的新 Sentinel,所以才需要建立订阅连接,而相互已知的 Sentinel 只要使用命令连接来进行通信就足够了

七、检测主观下线状态

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

在这里插入图片描述

在图 16-17 展示的例子中,带箭头的连线显示了 Sentinel1 和 Sentinel2 是如何向实例发送 PING 命令的:

  • Sentinel1 将向 Sentinel2、主服务器 master、从服务器 slave1 和 slave2 发送 PING 命令
  • Sentinel2 将向 Sentinel1、主服务器 master、从服务器 slave1 和 slave2 发送 PING 命令

实例对 PING 命令的回复可以分为以下两种情况:

  • 有效回复:实例返回 +PONG、-LOADING、-MASTERDOWN 三种回复的其中一种
  • 无效回复:实例返回除 +PONG、-LOADING、-MASTERDOWN 三种回复之外的其他回复或者在指定时限内没有返回任何回复

Sentinel 配置文件中的 down-after-milliseconds 选项指定了 Sentinel 判断实例进入主观下线所需的时间长度:如果一个实例在 down-after-milliseconds 毫秒内,连续向 Sentinel 返回无效回复,那么 Sentinel 会修改这个实例所对应的实例结构,在结构的 flags 属性中打开 SRI_S_DOWN 标识,以此来表示这个实例已经进入主观下线状态

以图 16-17 展示的情况为例子,如果配置文件指定 Sentinel1 的 down-after-milliseconds 选项的值为 50000 毫秒,那么当主服务器 master 连续 50000 毫秒都向 Sentinel1 返回无效回复时,Sentinel1 就会将 master 标记为主观下线,并在 master 所对应的实例结构的 flags 属性中打开 SRI_S_DOWN 标识,如图 16-18 所示

在这里插入图片描述

主观下线时长选项的作用范围

用户设置得 down-after-milliseconds 选项的值,不仅会被 Sentinel 用来判断主服务器的主观下线状态,还会被用于判断主服务器属下的所有从服务器,以及所有同样监视这个主服务器的其他 Sentinel 的主观下线状态。举个例子,如果用户向 Sentinel 设置了以下配置:

sentinel monitor master 127.0.0.1 6379 2
sentinel down-after-milliseconds master 50000

那么 50000 毫秒不仅会成为 Sentinel 判断 master 进入主观下线的标准,还会成为 Sentinel 判断 master 属下所有从服务器,以及所有同样监视 master 的其他 Sentinel 进入主观下线的标准

多个 Sentinel 设置的主观下线时长可能不同

down-after-milliseconds 选项另一个需要注意的地方是,对于监视同一个主服务器的多个 Sentinel 来说,这些 Sentinel 所设置的 down-after-milliseconds 选项的值也可能不同,因此,当一个 Sentinel 将主服务器判断为主观下线时,其他 Sentinel 可能仍然会认为主服务器处于在线状态。举个例子,如果 Sentinel1 载入了以下配置:

sentinel monitor master 127.0.0.1 6379 2
sentinel down-after-milliseconds master 50000

而 Sentinel2 则载入了以下配置:

sentinel monitor master 127.0.0.1 6379 2
sentinel down-after-milliseconds master 10000

那么当 master 的断线时长超过了 10000 毫秒之后,Sentinel2 会将 master 判断为主观下线,而 Sentienl1 却认为 master 仍然在线。只有当 master 的断线时长超过 50000 毫秒之后,Sentinel1 和 Sentinel2 才会都认为 master 进入了主观下线状态

八、检测客观下线状态

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

1.发送 SENTINEL is-master-down-by-addr 命令

Sentinel 使用:

SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>

命令询问其他 Sentinel 是否同意主服务器已下线,命令中的各个参数的意义如表 16-4 所示

在这里插入图片描述

举个例子,如果被 Sentinel 判断为主观下线的主服务器的 IP 为 127.0.0.1,端口号为 6379,并且 Sentinel 当前的配置纪元为 0,那么 Sentinel 将向其他 Sentinel 发送以下命令:

SENTINEL is-master-down-by-addr 127.0.0.1 6379 0 *

2.接收 SENTINEL is-master-down-by-addr 命令

当一个 Sentinel(目标 Sentinel)接收到另一个 Sentinel(源 Sentinel)发来的 SENTINEL is-master-down-by 命令时,目标 Sentinel 会分析并取出命请求中包含的各个参数,并根据其中的主服务器 IP 和端口号,检查主服务器是否已下线,然后向源 Sentinel 返回一条包含三个参数的 Multi Bulk 回复作为 SENTINEL is-master-down-by 命令的回复:

1) <down_state>
2) <leader_runid>
3) <leader_epoch>

表 16-5 分别记录了这三个参数的意义

在这里插入图片描述

举个例子,如果一个 Sentinel 返回以下回复作为 SENTINEL is-master-down-by-addr 命令的回复:

1) 1
2) *
3) 0

那么说明 Sentinel 也同意主服务器已下线

3.接收 SENTINEL is-master-down-by-addr 命令的回复

根据其他 Sentinel 发回的 SENTINEL is-master-down-by-addr 命令回复,Sentinel 将统计其他 Sentinel 同意主服务器已下线的数量,当这一数量达到配置指定的判断客观下线所需的数量时,Sentinel 会将主服务器实例结构 flags 属性的 SRI_O_DOWN 标识打开,表示主服务器已经进入客观下线状态,如图 16-19 所示

在这里插入图片描述

客观下线状态的判断条件

当认为主服务器已经进入下线状态的 Sentinel 的数量,超过 Sentinel 配置中设置得 quorum 参数的值,那么该 Sentinel 就会认为主服务器已经进入客观下线状态。比如说,如果 Sentinel 在启动时载入了以下配置:

sentinel monitor master 127.0.0.1 6379 2

那么包括当前 Sentinel 在内,只要总共有两个 Sentinel 认为主服务器已经进入了下线状态,那么当前 Sentinel 就将主服务器判断为客观下线。又比如说,如果 Sentinel 在启动时载入了以下配置:

sentinel monitor master 127.0.0.1 6379 5

那么包括当前 Sentinel 在内,总共要有五个 Sentinel 都认为主服务器已经下线,当前 Sentinel 才会将主服务器判断为客观下线

不同 Sentinel 判断客观下线的条件可能不同

对于监视同一个主服务器的多个 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 的配置纪元(configuration epoch)的值都会自增一次。配置纪元实际上就是一个计数器,并没有什么特别的
  • 在一个配置纪元里面,所有 Sentinel 都有一次将某个 Sentinel 设置为局部领头 Sentinel 的机会,并且局部领头 一旦设置,在这个配置纪元里面就不能再更改
  • 每个发现主服务器进入客观下线的 Sentinel 都会要求其他 Sentinel 将自己设置为局部领头 Sentinel
  • 当一个 Sentinel(源 Sentinel)向另一个 Sentinel(目标 Sentinel)发送 SENTINEL is-master-down-by-addr 命令,并且命令中的 runid 参数不是 * 符号而是源 Sentinel 的运行 ID 时,这表示源 Sentinel 要求目标 Sentinel 将前者设置为后者的局部领头 Sentinel
  • Sentinel 设置局部领头 Sentienl 的规则是先到先得:最先向目标 Sentinel 发送设置要求的源 Sentienl 将成为目标 Sentinel 的局部领头 Sentinel,而之后接收到的所有设置要求都会被目标 Sentinel 拒绝
  • 目标 Sentinel 在接收到 SENTINEL is-master-down-by-addr 命令之后,将向源 Sentinel 返回一条命令回复,回复中的 leader_runid 参数和 leader_epoch 参数分别记录了目标 Sentinel 的局部领头 Sentinel 的运行 ID 和配置纪元
  • 源 Sentinel 在接收到目标 Sentinel 返回的命令回复之后,会检查回复中 leader_epoch 参数的值和自己的配置纪元是否相同,如果相同的话,那么源 Sentinel 继续取出回复中的 leader_runid 参数,如果 leader_runid 参数的值和源 Sentinel 的运行 ID 一致,那么表示目标 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,那么各个 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 is-master-down-by-addr 127.0.0.1 6379 e955b4c85598ef5b5f055bc7ebfd5e8828dbed4fa

如果接收到这个命令的 Sentinel 还没有设置局部领头 Sentinel 的话,它就会将运行 ID 为 e955b4c85598ef5b5f055bc7ebfd5e828dbed4fa 的 Sentinel 设置为自己的局部领头 Sentinel,并返回类似以下的命令回复:

1) 1
2) e955b4c85598ef5b5f055bc7ebfd5e8828dbed4fa
3) 0

然后接收到命令回复的 Sentinel 就可以根据这一回复,统计出有多少个 Sentinel 将自己设置成了局部领头 Sentinel

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

十、故障转移

在选举产生出领头 Sentinel 之后,领头 Sentinel 将对已下线的主服务器执行故障转移操作,该操作包含以下三个步骤:

  1. 在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器
  2. 让已下线主服务器属下的所有从服务器改为复制新的主服务器
  3. 将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器

1.选出新的主服务器

故障转移操作第一步要做的就是在已下线主服务器属下的所有从服务器中,挑选出一个状态良好、数据完整的从服务器,然后向这个从服务器发送 SLAVEOF no one 命令,将这个从服务器转换为主服务器

新的主服务器是怎样挑选出来的

领头 Sentinel 会将已下线出服务器的所有从服务器保存到一个列表里面,然后按照以下规则,一项一项地对列表进行过滤:

  1. 删除列表中所有处于下线或者断线状态的从服务器,这可以保证列表中剩余的从服务器都是正常在线的
  2. 删除列表中所有最近五秒内没有回复过领头 Sentinel 的 INFO 命令的从服务器,这可以保证列表中剩余的从服务器都是最近成功进行过通信的
  3. 删除所有与已下线主服务器连接断开超过 down-after-milliseconds * 10 毫秒的从服务器:down-after-milliseconds 选项指定了判断主服务器下线所需的时间,而删除断开时长超过 down-after-milliseconds * 10 毫秒的从服务器,则可以保证列表中剩余的从服务器都没有过早地与主服务器断开连接,换句话说,列表中剩余的从服务器保存的数据都是比较新的

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

如果有多个具有相同最高优先级的从服务器,那么领头 Sentinel 将按照从服务器的复制偏移量,对具有相同最高优先级的所有从服务器进行排序,并选出其中偏移量最大的从服务器(复制偏移量最大的从服务器就是保存着最新数据的从服务器)

最后,如果有多个优先级最高、复制偏移量最大的从服务器,那么领头 Sentinel 将按照运行 ID 对这些从服务器及逆行排序,并选出其中运行 ID 最小的从服务器

图 16-22 展示了在一次故障转移操作中,领头 Sentienl 向被选中的从服务器 server2 发送 SLAVEOF no one 命令的情形

在这里插入图片描述

在发送 SLAVEOF no one 命令之后,领头 Sentinel 会以每秒一次的频率(平时是每十秒一次),向被升级的从服务器发送 INFO 命令,并观察命令回复中的角色(role)信息,当被升级服务器的 role 从原来的 slave 变为 master 时,领头 Sentinel 就知道被选中的从服务器已经顺利升级为主服务器了

例如,在图 16-22 展示的例子中,领头 Sentinel 会一直向 server2 发送 INFO 命令,当 server2 返回的命令回复从:

# Replication
role:slave
...
# Ohter sections
...

变为:

# Replication
role:master
...
# Ohter sections
...

的时候,领头 Sentinel 就知道 server2 已经成功升级为主服务器了

图 16-23 展示额 server2 升级成功之后,各个服务器和领头 Sentinel 的样子

在这里插入图片描述

2.修改从服务器的复制目标

当新的主服务器出现之后,领头 Sentinel 下一步要做的就是,让已下线主服务器属下的所有从服务器去复制新的主服务器,这一动作可以通过向从服务器发送 SLAVEOF 命令来实现

图 16-24 展示了在故障转移操作中,领头 Sentinel 向已下线主服务器 server1 的两个从服务器 server3 和 server4 发送 SLAVEOF 命令,让它们复制新的主服务器 server2 的例子

在这里插入图片描述

图 16-25 展示了 server3 和 server4 成为 server2 的从服务器之后,各个服务器以及领头 Sentinel 的样子

在这里插入图片描述

3.将旧的主服务器变为从服务器

故障转移操作最后要做的就是,将已下线的主服务器设置为新的主服务器的从服务器。比如说,图 16-26 就展示了被领头 Sentinel 设置为从服务器之后,服务器 sever1 的样子

因为旧的主服务器就已经下线,所以这种设置是保存在 server1 对应的实例结构里面的,当 server1 重新上线时,Sentinel 就会向它发送 SLAVEOF 命令,让它成为 server2 的从服务器

例如,图 16-27 就展示了 server1 重新上线并成为 server2 的从服务器的例子

在这里插入图片描述

十一、重点回顾

  • Sentinel 只是一个运行在特俗模式下的 Redis 服务器,它使用了和普通模式不同的命令表,所以 Sentinel 模式能够使用的命令和普通 Redis 服务器能够使用的命令不同
  • Sentinel 会读入用户指定的配置文件,为每个要监视的主服务器创建相应的实例结构,并创建连向主服务器的命令连接和订阅连接,其中命令连接用于向主服务器发送命令请求,而订阅连接则用于接收指定频道的消息
  • Sentinel 通过向主服务器发送 INFO 命令来获得主服务器属下所有从服务器的地址信息,并为这些从服务器创建相应的实例结构,以及连向这些从服务器的命令连接和订阅连接
  • 在一般情况下,Sentinel 以每十秒一次的频率向被监视的主服务器和从服务器发送 INFO 命令,当主服务器处于下线状态,或者 Sentinel 正在对主服务器进行故障转移操作时,Sentinel 向从服务器发送 INFO 命令的频率会改为每秒一次
  • 对于监视同一个主服务器和从服务器的多个 Sentinel 来说,它们会以每两秒一次的频率,通过向被监视服务器的 __sentinel__:hello 频道发送消息来向其他 Sentinel 宣告自己的存在
  • 每个 Sentinel 也会从 __sentinel__:hello 频道中接收其他 Sentinel 发来的信息,并根据这些信息为其他 Sentinel 创建相应的实例结构,以及命令连接
  • Sentinel 只会与主服务器和从服务器创建命令连接和订阅连接,Sentinel 与 Sentinel 之间只创建命令连接
  • Sentienl 以每秒一次的频率向实例(包括主服务器、从服务器、其他 Sentinel)发送 PING 命令,并根据实例对 PING 命令的回复来判断实例是否在线,当一个实例在指定的时长中连续向 Sentinel 发送无效回复时,Sentinel 会将这个实例判断为主观下线
  • 当 Sentinel 将一个主服务器判断为主观下线时,它会向同样监视这个主服务器的其他 Sentinel 进行询问,看它们是否同意这个主服务器进入主观下线状态
  • 当 Sentinel 收集到足够多的主观下线投票之后,它会将主服务器判断为客观下线,并发起一次针对主服务器的故障转移

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/95635.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息