【Redis】 Sentinel 机制
【Redis】 Sentinel 机制
Metadata
title: 【Redis】 Sentinel 机制
date: 2023-07-09 08:46
tags:
- 行动阶段/完成
- 主题场景/数据存储
- 笔记空间/KnowladgeSpace/ProgramSpace/BasicsSpace
- 细化主题/数据存储/Redis
categories:
- 数据存储
keywords:
- 数据存储/Redis
description: 【Redis】 Sentinel 机制
概述
Sentinel(哨兵)是 Redis 的高可用性的解决方案,由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器以及属下的所有从服务器。当主服务器下线时,自动将下线的某个主服务器属下的某个从服务器升级为新的主服务器。从而实现故障转移,当原来的主服务器重新上线时,会被降级为从服务器。
下面展示了哨兵监视主从的状态:
- Sentinel只是一个运行在特殊模式下的Redis服务器,它使用了和普通模式不同的命令表,所以Sentinel模式能够使用的命令和普通Redis服务器能够使用的命令不同。
- Sentinel会读入用户指定的配置文件,为每个要被监视的主服务器创建相应的实例结构,并创建连向主服务器的命令连接和订阅连接,其中命令连接用于向主服务器发送命令请求,而订阅连接则用于接收指定频道的消息。
- Sentinel通过向主服务器发送INFO命令来获得主服务器属下所有从服务器的地址信息,并为这些从服务器创建相应的实例结构,以及连向这些从服务器的命令连接和订阅连接。
- 在一般情况下,Sentinel以每十秒一次的频率向被监视的主服务器和从服务器发送INFO命令,当主服务器处于下线状态,或者Sentinel正在对主服务器进行故障转移操作时,Sentinel向从服务器发送INFO命令的频率会改为每秒一次。
- 对于监视同一个主服务器和从服务器的多个Sentinel来说,它们会以每两秒一次的频率,通过向被监视服务器的__sentinel__:hello频道发送消息来向其他Sentinel宣告自己的存在。
- 每个Sentinel也会从__sentinel__:hello频道中接收其他Sentinel发来的信息,并根据这些信息为其他Sentinel创建相应的实例结构,以及命令连接。
- Sentinel只会与主服务器和从服务器创建命令连接和订阅连接,Sentinel与Sentinel之间则只创建命令连接。
- Sentinel以每秒一次的频率向实例(包括主服务器、从服务器、其他Sentinel)发送PING命令,并根据实例对PING命令的回复来判断实例是否在线,当一个实例在指定的时长中连续向Sentinel发送无效回复时,Sentinel会将这个实例判断为主观下线。
- 当Sentinel将一个主服务器判断为主观下线时,它会向同样监视这个主服务器的其他Sentinel进行询问,看它们是否同意这个主服务器已经进入主观下线状态。
- 当Sentinel收集到足够多的主观下线投票之后,它会将主服务器判断为客观下线,并发起一次针对主服务器的故障转移操作。
下面主要讲解 Sentinel 系统对主服务器执行故障转移的整个过程。
启动并初始化 Sentinel
启动 Sentinel 有两种方式:
redis-sentinel /path/to/your/sentinel.conf
redis-server /path/to/your/sentinel.conf --sentinel
俩命令效果相同,启动时需要执行以下步骤:
- 初始化服务器。
- 将普通 Redis 服务器使用的代码替换成 Sentinel 专用代码。
- 初始化 Sentinel 状态。
- 根据配置文件,初始化 Sentinel 的监视主服务器列表。
- 创建连向主服务器的网络连接。
初始化服务器
Sentinel 实际上是一个特殊的 Redis 服务器,所以很多地方和 Redis 服务器的初始化有些类似。只不过少了 RDB 或 AOF 文件的载入等操作。
使用 Sentinel 专用代码
将加载的常量,命令表(决定了 Sentinel 可以执行哪些命令)等替换为 Sentinel 专用的。
初始化 Sentinel 状态
初始化一个sentinel.c/sentinelState
结构,记录 Sentinel 的状态,保存了服务器中所有与 Sentinel 相关的状态:
struct sentinelState{
//当前纪元,选举计数器,用于实现故障转移
uint64_ t current_ epoch;
//(重点)保存了所有被这个sentinel监视的主服务器
//字典的键是主服务器的名字,值是一个指向sentine1RedisInstance结构的指针
dict masters;
//是否进入了TILT模式
int tilt;
//目前正在执行的脚本的数量
int running_ scripts;
//进入TILT模式的时间
mstime_ _t tilt_ start_ time;
//最后一次执行时间处理器的时间
mstime_ t previous_ time ;
//一个FIFO队列,包含了所有需要执行的用户脚本
list *scripts_ queue;
}sentinel;
初始化 master 属性
master 实例包括主服务器、从服务器或另一个 Sentinel。实例结构如下,了解一下,故障转移的可以先不关注:
typedef struct sentinelRedisInstance {
//标识值,记录了实例的类型,以及该实例的当前状态.
int flags;
//实例的名字.
//主服务器的名字由用户在配置文件中设置
//从服务器以及Sentinel 的名字由Sentinel 自动设置
//格式为ip:port
char *name;
//实例的运行ID .
char *runid;
//配置纪元,用于实现故障转移
uint64_t config_epoch;
//实例的地址
sentinelAddr *addr;
// SENTINEL down-after-milliseconds 选项设定的值
//实例无响应多少毫秒之后才会被判断为主观下线
mstime_t down_after_period;
//判断这个实例为客观下线所需的支持投票数量
int quorum;
//在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
int paral1el_syncs;
//刷新故障迁移状态的最大时限
mstime_t failover_timeout;
// ...
} sentinelRedisInstance;
创建连向主服务器的网络连接
Sentinel 会为监视的主服务器创建两个异步网络连接:
- 命令连接:专用于向主服务器发送命令,接收命令回复。
- 订阅连接:专用于订阅主服务器
__sentinel__:hello
频道。(由于 Redis 的发布订阅消息不会保存,客户端断线就会丢失,为了不丢失,必须使用专门的频道连接)
获取主从服务器信息
Sentinel 默认 10 秒一次通过命令连接被监视的主服务器并发送 INFO 命令,获取主服务器信息。主要获取主服务器本身信息(如服务器运行 ID),下属从服务器信息(如 ip,port,offset)。对应属性进行更新,如果没有某个从服务器新信息就会创建一个实例结构,放到主服务器的 slaves 字典中,键为 ip + 端口,值为 sentinelRedisInstance。除了创建新实例,还会创建连接到从服务器的命令连接和订阅连接。
向主服务器和从服务器发送消息
sentinel 默认以两秒一次,向服务器的__sentinel__:hello
频道发送消息,命令:
PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
参数包含 sentinel 本身(s__…)和主服务器(m…)的运行 ID,ip,端口号,配置纪元等参数。
接收来自主服务器和从服务器的频道信息
Sentinel 与一个主服务器或从服务器建立订阅连接后,会发送SUBSCRIBE _sentinel_:hello
命令。
也就是 Sentinel 通过命令连接发送信息到频道,又通过订阅连接接收频道中的信息。一个 Sentinel 发的信息也会被其他 Sentinel 接收,根据信息记录的 Sentinel 运行 id 和接收信息的 Sentinel 运行 id 是否相同,来决定是否处理这条消息。通过这种透明的沟通机制,Sentinel 可以对各自监听的服务器信息进行更新。
更新 sentinels 字典
根据接收而来的消息,Sentinel 会更新实例结构中 sentinels 字典保存的所有 Sentinel 实例的信息。键为 Sentinel 的 ip + 端口,值为某个 Sentinel 的实例。消息接收者会检查发送消息的 Sentinel(源 sentinel)结构是否在 sentinels 字典存在则更新,没有则创建实例,和自己相同的 sentinel 不会被放入。
typedef struct sentinelRedisInstance{
dict *sentinels;
...
}
通过这种发布订阅的方式,Sentinel 不需要各自发信息告诉对方,而是监视同一个主服务器的多个 Sentinel 自动发现对方。
创建连向其他 Sentinel 的命令连接
sentinel 也会为对方互相创建命令连接,最终监视同一主服务器的多个 sentinel 会形成一个网络。但他们互相之间不会创建订阅连接,因为他们通过主或从服务器发来的频道来发现未知的 sentinel。
检测主观下线状态
Sentinel 默认每秒与创建命令连接的实例(主服务器,从服务器,其他 sentinel)发送 PING 命令,通过回复判断是否在线。如果实例返回除了+PONG
,-LOADING
,-MASTERRDOWN
之外的回复或未及时回复,就认为是无效回复。根据配置文件的down-after-milliseconds
指定的主观下线所需时长内是否一直无效回复,来判断实例是否已经主观下线。下线了就将实例的的 flags 标识属性打开SRI_S_DOWN
标识。由于每个 Sentinel 中的主观下线时间配置都可以不同,所有有可能某个 Sentinel 判断主观下线时,另一个 Sentinel 认为在线状态。
检查客观下线状态
当 Sentinel 判断主服务器为主观下线时,还会向其他 Sentinel 询问,得到足量数据已下线判断后,就会判定服务器为客观下线,并执行故障转移。
发送 sentinel is-master-down-by-addr 命令
Sentinel 使用:SENTINEL is-master-down-by-addr <ip> <port> <current. epoch> <runid>
命令询问其他 Sentinel 是否同意主服务器下线。这些参数分别是 Sentinel 的 ip,端口,配置纪元和运行 id。
接收 sentinel is-master-down-by-addr 命令
其他接收并返回三个参数的 Multi Bulk 回复:
:是对主服务器的检查结果,1 表示已下线;0 表示未下线。 :如果是 *,表示该命令用于检测服务器状态;如果是 Sentinel 的运行 id 用于选举领头 Sentinel。 :选举计数器,用于选举领头 sentinel。
接收 sentinel is-master-down-by-addr 命令的回复
统计其他 Sentinel 同意主服务器已下线数量,当数量超过配置值(quorum 参数)时,sentinel 会将主服务器实例的 flags 属性的SRI_O_DOWN
属性打开,表示已进入客观下线状态。
typedef struct sentinelRedisInstance {
//判断这个实例为客观下线所需的支持投票数量
int quorum;
...
} sentinelRedisInstance;
选举领头 Sentinel
当主服务器被判断为客观下线时,sentinel 会协商选举领头 sentinel,并由领头 sentinel 对下线主服务器执行故障转移操作。
当SENTINEL is-master-down-by-addr
命令已经确认主服务器客观下线时,Sentinel 还会再发送带有选举性质的该命令,并且带上自己的运行 ID。如果接收命令的 Sentinel 还没设置局部领头时,就会将这个运行 ID 作为自己的 Multi Bulk 回复参数。根据回复参数来判断多少 sentinel 将自己设置为局部领头。可能根据网络延迟,有的 Sentinel 命令比其他 Sentinel 都先到达,并且胜出(必须有半数以上的票),那么就由它负责故障转移。一次选举没有产生,一段时间后再次选举,直到选出。
故障转移
故障转移包括 3 步:
- 在已下线的主服务器属下从服务器里选出一个将其转为主服务器。
- 让其他从服务器都复制新主服务器。
- 当原来的主服务器再次上线时,让他成为新主服务器的从服务器。
选出新主服务器
如何选新的主服务器?Sentinel 会将所有从服务器放入列表,一项一项过滤:
- 删除处于下线或断线状态的从服务器。
- 删除最近 5 秒没有回复过领头
sentinel INFO
命令的从服务器。 - 删除与已下线服务器段开时间超过
down-after-milliseconds*10
毫秒的从服务器。
然后根据优先级排序,相同则选偏移量最大的,相同则选运行 ID 最小的。
选出来之后,对这个从服务器发送SLAVEOF no one
命令,然后以每秒一次的频率向它发送INFO
命令,观察返回的 role 属性如果变成 master,就表示顺利升级为主服务器了。
修改从服务器的复制目标
向所有其他从服务器发送SLAVEOF
命令,让他们都去复制新的主服务器。
将旧的主服务器变为从服务器
当原来的主服务器上线时,Sentinel 就会向它发送SLAVEOF
命令,让他成为新主服务器的从服务器。