《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚

本文已收录于专栏
??《Redis精通系列》??
上千人点赞收藏,全套Redis学习资料,大厂必备技能!
目录
1、简介
2、Sentinel初始化与网络连接
2.1 初始化Sentinel服务器
2.2 替换普通Redis代码为Sentinel的专用代码
2.3 初始化Sentinel状态
2.4 初始化Sentinel监视的主服务器列表
2.5 创建连接主服务器的网络连接
2.6 创建连接从服务器的网络连接
2.7 创建Sentinel之间的网络连接
3、Sentinel工作
3.1 检测Master是否主观下线
3.2 检测Master是否客观下线
3.3 选举领头Sentinel
3.4 故障转移
1、简介 主从复制奠定了Redis分布式的基础,但是普通的主从复制并不能达到高可用的状态。在普通的主从复制模式下,如果主服务器宕机,就只能通过运维人员手动切换主服务器,很显然这种方案并不可取。
针对上述情况,Redis官方推出了可抵抗节点故障的高可用方案——Redis Sentinel(哨兵)。Redis Sentinel(哨兵):由一个或多个Sentinel实例组成的Sentinel系统,它可以监视任意多个主从服务器,当监视的主服务器宕机时,自动下线主服务器,并且择优选取从服务器升级为新的主服务器。
?
如下示例:当旧Master下线时长超过用户设定的下线时长上限,Sentinel系统就会对旧Master执行故障转移操作,故障转移操作包含三个步骤:

  1. 在Slave中选择数据最新的作为新的Master
  2. 向其他Slave发送新的复制指令,让其他从服务器成为新的Master的Slave
  3. 继续监视旧Master,如果其上线则将旧Master设置为新Master的Slave
《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚
文章图片

本文基于如下资源清单进行开展:
IP地址 节点角色 端口
192.168.211.104 Redis Master/ Sentinel 6379/26379
192.168.211.105 Redis Slave/ Sentinel 6379/26379
192.168.211.106 Redis Slave/ Sentinel 6379/26379
?
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 yes port 26379 protected-mode no dir "/usr/local/soft/redis-6.2.4/sentinel-tmp" sentinel monitor redis-master 192.168.211.104 6379 2 sentinel down-after-milliseconds redis-master 30000 sentinel failover-timeout redis-master 180000 sentinel 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;

根据上面的一主二从配置将会得到如下实例结构:
《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚
文章图片

2.5 创建连接主服务器的网络连接
当实例结构初始化完成之后,Sentinel将会开始创建连接Master的网络连接,这一步Sentinel将成为Master的客户端。
Sentinel和Master之间会创建一个命令连接和一个订阅连接:
  • 命令连接用于获取主从信息
  • 订阅连接用于Sentinel之间进行信息广播,每个Sentinel和自己监视的主从服务器之间会订阅sentinel:hello频道(注意Sentinel之间不会创建订阅连接,它们通过订阅sentinel:hello频道来获取其他Sentinel的初始信息)
《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚
文章图片

Sentinel在创建命令连接完成之后,每隔10秒钟向Master发送一次INFO指令,通过Master的回复信息可以获得两方面的知识:
  • Master本身的信息
  • Master下的Slave信息
《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚
文章图片


2.6 创建连接从服务器的网络连接
根据主服务获取从服务器信息,Sentinel可以创建到Slave的网络连接,Sentinel和Slave之间也会创建命令连接和订阅连接。
《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚
文章图片

当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
此时实例结构信息如下所示:
《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚
文章图片

2.7 创建Sentinel之间的网络连接
此时是不是还有疑问,Sentinel之间是怎么互相发现对方并且相互通信的,这个就和上面Sentinel与自己监视的主从之间订阅sentinel:hello频道有关了。
Sentinel会与自己监视的所有Master和Slave之间订阅sentinel:hello频道,并且Sentinel每隔2秒钟向sentinel:hello频道发送一条消息,消息内容如下:
PUBLISH _sentinel_:hello ",,,,,,,"
其中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之间不会创建订阅连接,它们只会创建命令连接:
《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚
文章图片

此时实例结构信息如下所示:
《Redis精通系列》|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
《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚
文章图片


3.2 检测Master是否客观下线
当前Sentinel认为其下线只能处于主观下线状态,要想判断当前Master是否客观下线,还需要询问其他Sentinel,并且所有认为Master主观下线或者客观下线的总和需要达到quorum配置的值,当前Sentinel才会将Master标志为客观下线。
《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚
文章图片

当前Sentinel向sentinelRedisInstance实例中的其他Sentinel发送如下命令:
SENTINEL is-master-down-by-addr

  • 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
《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚
文章图片


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

此时的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。
?
【《Redis精通系列》|Sentinel(哨兵)——一文彻底搞清楚】到这里Sentinel的的工作流程就算是结束了,如果新master下线,则循环流程即可!

    推荐阅读