写点什么

Redis 值 Sentinel(哨兵)详述,图文并茂才能浅显易懂

作者:李子捌
  • 2021 年 11 月 27 日
  • 本文字数:4947 字

    阅读完需:约 16 分钟

Redis值Sentinel(哨兵)详述,图文并茂才能浅显易懂

1、简介

主从复制奠定了 Redis 分布式的基础,但是普通的主从复制并不能达到高可用的状态。在普通的主从复制模式下,如果主服务器宕机,就只能通过运维人员手动切换主服务器,很显然这种方案并不可取。针对上述情况,Redis 官方推出了可抵抗节点故障的高可用方案——Redis Sentinel(哨兵)。Redis Sentinel(哨兵):由一个或多个 Sentinel 实例组成的 Sentinel 系统,它可以监视任意多个主从服务器,当监视的主服务器宕机时,自动下线主服务器,并且择优选取从服务器升级为新的主服务器。​


如下示例:当旧 Master 下线时长超过用户设定的下线时长上限,Sentinel 系统就会对旧 Master 执行故障转移操作,故障转移操作包含三个步骤:


  1. 在 Slave 中选择数据最新的作为新的 Master

  2. 向其他 Slave 发送新的复制指令,让其他从服务器成为新的 Master 的 Slave

  3. 继续监视旧 Master,如果其上线则将旧 Master 设置为新 Master 的 Slave



本文基于如下资源清单进行开展:



2、Sentinel 初始化与网络连接

Sentinel 并没有什么特别神奇的地方,它就是一个更加简单的 Redis 服务器,在 Sentinel 启动的时候它会加载不同的命令表和配置文件,因此从本质上来讲 Sentinel 就是一个拥有较少命令和部分特殊功能的 Redis 服务。当一个 Sentinel 启动时它需要经历如下步骤:


  1. 初始化 Sentinel 服务器

  2. 替换普通 Redis 代码为 Sentinel 的专用代码

  3. 初始化 Sentinel 状态

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

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

  6. 根据主服务获取从服务器信息,创建连接从服务器的网络连接

  7. 根据发布/订阅获取 Sentinel 信息,创建 Sentinel 之间的网络连接

2.1 初始化 Sentinel 服务器

Sentinel 本质上就是一个 Redis 服务器,因此启动 Sentinel 需要启动一个 Redis 服务器,但是 Sentinel 并不需要读取 RDB/AOF 文件来还原数据状态。​

2.2 替换普通 Redis 代码为 Sentinel 的专用代码

Sentinel 用于较少的 Redis 命令,大部分命令在 Sentinel 客户端都不支持,并且 Sentinel 拥有一些特殊的功能,这些需要 Sentinel 在启动时将 Redis 服务器使用的代码替换为 Sentinel 的专用代码。在此期间 Sentinel 会载入与普通 Redis 服务器不同的命令表。Sentinel 不支持 SET、DBSIZE 等命令;保留支持 PING、PSUBSCRIBE、SUBSCRIBE、UNSUBSCRIBE、INFO 等指令;这些指令在 Sentinel 工作中提供了保障。​

2.3 初始化 Sentinel 状态

装载 Sentinel 的特有代码之后,Sentinel 会初始化 sentinelState 结构,该结构用于存储 Sentinel 相关的状态信息,其中最重要的就是 masters 字典。


struct sentinelState {       //当前纪元,故障转移使用  uint64_t current_epoch;       // Sentinel监视的主服务器信息     // key -> 主服务器名称     // value -> 指向sentinelRedisInstance指针    dict *masters;     // ...} sentinel;
复制代码

2.4 初始化 Sentinel 监视的主服务器列表

Sentinel 监视的主服务器列表保存在 sentinelState 的 masters 字典中,当 sentinelState 创建之后,开始对 Sentinel 监视的主服务器列表进行初始化。


  • masters 的 key 是主服务的名字

  • masters 的 value 是一个指向 sentinelRedisInstance 指针


主服务器的名字由我们 sentinel.conf 配置文件指定,如下主服务器名字为 redis-master(我这里是一主二从的配置):


daemonize yesport 26379protected-mode nodir "/usr/local/soft/redis-6.2.4/sentinel-tmp"sentinel monitor redis-master 192.168.211.104 6379 2sentinel down-after-milliseconds redis-master 30000sentinel failover-timeout redis-master 180000sentinel parallel-syncs redis-master 1
复制代码


sentinelRedisInstance 实例保存了 Redis 服务器的信息(主服务器、从服务器、Sentinel 信息都保存在这个实例中)。


typedef struct sentinelRedisInstance {      // 标识值,标识当前实例的类型和状态。如SRI_MASTER、SRI_SLVAE、SRI_SENTINEL    int flags;        // 实例名称 主服务器为用户配置实例名称、从服务器和Sentinel为ip:port    char *name;        // 服务器运行ID    char *runid;        //配置纪元,故障转移使用  uint64_t config_epoch;         // 实例地址    sentinelAddr *addr;        // 实例判断为主观下线的时长 sentinel down-after-milliseconds redis-master 30000    mstime_t down_after_period;         // 实例判断为客观下线所需支持的投票数 sentinel monitor redis-master 192.168.211.104 6379 2    int quorum;        // 执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量 sentinel parallel-syncs redis-master 1    int parallel-syncs;        // 刷新故障迁移状态的最大时限 sentinel failover-timeout redis-master 180000  mstime_t failover_timeout;        // ...} sentinelRedisInstance;
复制代码


根据上面的一主二从配置将会得到如下实例结构:


2.5 创建连接主服务器的网络连接

当实例结构初始化完成之后,Sentinel 将会开始创建连接 Master 的网络连接,这一步 Sentinel 将成为 Master 的客户端。Sentinel 和 Master 之间会创建一个命令连接和一个订阅连接:


  • 命令连接用于获取主从信息

  • 订阅连接用于 Sentinel 之间进行信息广播,每个 Sentinel 和自己监视的主从服务器之间会订阅_sentinel_:hello 频道(注意 Sentinel 之间不会创建订阅连接,它们通过订阅_sentinel_:hello 频道来获取其他 Sentinel 的初始信息)



Sentinel 在创建命令连接完成之后,每隔 10 秒钟向 Master 发送一次 INFO 指令,通过 Master 的回复信息可以获得两方面的知识:


  • Master 本身的信息

  • Master 下的 Slave 信息



2.6 创建连接从服务器的网络连接

根据主服务获取从服务器信息,Sentinel 可以创建到 Slave 的网络连接,Sentinel 和 Slave 之间也会创建命令连接和订阅连接。



当 Sentinel 和 Slave 之间创建网络连接之后,Sentinel 成为了 Slave 的客户端,Sentinel 也会每隔 10 秒钟通过 INFO 指令请求 Slave 获取服务器信息。到这一步 Sentinel 获取到了 Master 和 Slave 的相关服务器数据。这其中比较重要的信息如下:


  • 服务器 ip 和 port

  • 服务器运行 id run id

  • 服务器角色 role

  • 服务器连接状态 mater_link_status

  • Slave 复制偏移量 slave_repl_offset(故障转移中选举新的 Master 需要使用)

  • Slave 优先级 slave_priority


此时实例结构信息如下所示:


2.7 创建 Sentinel 之间的网络连接

此时是不是还有疑问,Sentinel 之间是怎么互相发现对方并且相互通信的,这个就和上面 Sentinel 与自己监视的主从之间订阅_sentinel_:hello 频道有关了。Sentinel 会与自己监视的所有 Master 和 Slave 之间订阅_sentinel_:hello 频道,并且 Sentinel 每隔 2 秒钟向_sentinel_:hello 频道发送一条消息,消息内容如下:


PUBLISH sentinel:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_ip>,<m_port>,<m_runid>,<m_epoch>"


其中 s 代码 Sentinel,m 代表 Master;ip 表示 IP 地址,port 表示端口、runid 表示运行 id、epoch 表示配置纪元。​


多个 Sentinel 在配置文件中会配置相同的主服务器 ip 和端口信息,因此多个 Sentinel 均会订阅_sentinel_:hello 频道,通过频道接收到的信息就可获取到其他 Sentinel 的 ip 和 port,其中有如下两点需要注意:


  • 如果获取到的 runid 与 Sentinel 自己的 runid 相同,说明消息是自己发布的,直接丢弃

  • 如果不相同,则说明接收到的消息是其他 Sentinel 发布的,此时需要根据 ip 和 port 去更新或新增 Sentinel 实例数据


Sentinel 之间不会创建订阅连接,它们只会创建命令连接:



此时实例结构信息如下所示:



3、Sentinel 工作

Sentinel 最主要的工作就是监视 Redis 服务器,当 Master 实例超出预设的时限后切换新的 Master 实例。这其中有很多细节工作,大致分为检测 Master 是否主观下线、检测 Master 是否客观下线、选举领头 Sentinel、故障转移四个步骤。​

3.1 检测 Master 是否主观下线

Sentinel 每隔 1 秒钟,向 sentinelRedisInstance 实例中的所有 Master、Slave、Sentinel 发送 PING 命令,通过其他服务器的回复来判断其是否仍然在线。​


sentinel down-after-milliseconds redis-master 30000
复制代码


在 Sentinel 的配置文件中,当 Sentinel PING 的实例在连续 down-after-milliseconds 配置的时间内返回无效命令,则当前 Sentinel 认为其主观下线。Sentinel 的配置文件中配置的 down-after-milliseconds 将会对其 sentinelRedisInstance 实例中的所有 Master、Slave、Sentinel 都适应。


无效指令指的是+PONG、-LOADING、-MASTERDOWN 之外的其他指令,包括无响应


如果当前 Sentinel 检测到 Master 处于主观下线状态,那么它将会修改其 sentinelRedisInstance 的 flags 为 SRI_S_DOWN



3.2 检测 Master 是否客观下线

当前 Sentinel 认为其下线只能处于主观下线状态,要想判断当前 Master 是否客观下线,还需要询问其他 Sentinel,并且所有认为 Master 主观下线或者客观下线的总和需要达到 quorum 配置的值,当前 Sentinel 才会将 Master 标志为客观下线。



当前 Sentinel 向 sentinelRedisInstance 实例中的其他 Sentinel 发送如下命令:


SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
复制代码


  • ip:被判断为主观下线的 Master 的 IP 地址

  • port:被判断为主观下线的 Master 的端口

  • current_epoch:当前 sentinel 的配置纪元

  • runid:当前 sentinel 的运行 id,runid


current_epoch 和 runid 均用于 Sentinel 的选举,Master 下线之后,需要选举一个领头 Sentinel 来选举一个新的 Master,current_epoch 和 runid 在其中发挥着重要作用,这个后续讲解。​


接收到命令的 Sentinel,会根据命令中的参数检查主服务器是否下线,检查完成后会返回如下三个参数:


  • down_state:检查结果 1 代表已下线、0 代表未下线

  • leader_runid:返回*代表判断是否下线,返回 runid 代表选举领头 Sentinel

  • leader_epoch:当 leader_runid 返回 runid 时,配置纪元会有值,否则一直返回 0


  1. 当 Sentinel 检测到 Master 处于主观下线时,询问其他 Sentinel 时会发送 current_epoch 和 runid,此时 current_epoch=0,runid=*

  2. 接收到命令的 Sentinel 返回其判断 Master 是否下线时 down_state = 1/0,leader_runid = *,leader_epoch=0



3.3 选举领头 Sentinel

down_state 返回 1,证明接收 is-master-down-by-addr 命令的 Sentinel 认为该 Master 也主观下线了,如果 down_state 返回 1 的数量(包括本身)大于等于 quorum(配置文件中配置的值),那么 Master 正式被当前 Sentinel 标记为客观下线。此时,Sentinel 会再次发送如下指令:


SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
复制代码


此时的 runid 将不再是 0,而是 Sentinel 自己的运行 id(runid)的值,表示当前 Sentinel 希望接收到 is-master-down-by-addr 命令的其他 Sentinel 将其设置为领头 Sentinel。这个设置是先到先得的,Sentinel 先接收到谁的设置请求,就将谁设置为领头 Sentinel。发送命令的 Sentinel 会根据其他 Sentinel 回复的结果来判断自己是否被该 Sentinel 设置为领头 Sentinel,如果 Sentinel 被其他 Sentinel 设置为领头 Sentinel 的数量超过半数 Sentinel(这个数量在 sentinelRedisInstance 的 sentinel 字典中可以获取),那么 Sentinel 会认为自己已经成为领头 Sentinel,并开始后续故障转移工作(由于需要半数,且每个 Sentinel 只会设置一个领头 Sentinel,那么只会出现一个领头 Sentinel,如果没有一个达到领头 Sentinel 的要求,Sentinel 将会重新选举直到领头 Sentinel 产生为止)。​

3.4 故障转移

故障转移将会交给领头 sentinel 全权负责,领头 sentinel 需要做如下事情:


  1. 从原先 master 的 slave 中,选择最佳的 slave 作为新的 master

  2. 让其他 slave 成为新的 master 的 slave

  3. 继续监听旧 master,如果其上线,则将其设置为新的 master 的 slave


这其中最难的一步是如果选择最佳的新 Master,领头 Sentinel 会做如下清洗和排序工作:


  1. 判断 slave 是否有下线的,如果有从 slave 列表中移除

  2. 删除 5 秒内未响应 sentinel 的 INFO 命令的 slave

  3. 删除与下线主服务器断线时间超过 down_after_milliseconds * 10 的所有从服务器

  4. 根据 slave 优先级 slave_priority,选择优先级最高的 slave 作为新 master

  5. 如果优先级相同,根据 slave 复制偏移量 slave_repl_offset,选择偏移量最大的 slave 作为新 master

  6. 如果偏移量相同,根据 slave 服务器运行 id run id 排序,选择 run id 最小的 slave 作为新 master


新的 Master 产生后,领头 sentinel 会向已下线主服务器的其他从服务器(不包括新 Master)发送 SLAVEOF ip port 命令,使其成为新 master 的 slave。​


到这里 Sentinel 的的工作流程就算是结束了,如果新 master 下线,则循环流程即可!

发布于: 2021 年 11 月 27 日阅读数: 9
用户头像

李子捌

关注

华为云享专家 2020.07.20 加入

公众号【李子捌】

评论

发布
暂无评论
Redis值Sentinel(哨兵)详述,图文并茂才能浅显易懂