Redis中sentinel故障转移的示例分析

76次阅读
没有评论

共计 10206 个字符,预计需要花费 26 分钟才能阅读完成。

这篇文章主要为大家展示了“Redis 中 sentinel 故障转移的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让丸趣 TV 小编带领大家一起研究并学习一下“Redis 中 sentinel 故障转移的示例分析”这篇文章吧。

当两台以上的 Redis 实例形成了主备关系,它们组成的集群就具备了一定的高可用性:当 master 发生故障的时候,slave 可以成为新的 master 对外提供读写服务,这种运营机制成为 failover。

那么谁来发现 master 的故障做 failover 决策?

一种方式是,保持一个 daemo 进程,监控着所有的 master-slave 节点,如下图所示:

一个 Redis 集群里面有一个 master 和两个 slave,这个 daemon 进程监控着这三个节点。但 daemon 为单节点,本身可用性无法保证。需要引入多 daemon,如下图所示:

多个 daemon 解决了可用性问题,但又出现了一致性问题,如何就某个 master 是否可用达成一致?例如上图两个 daemon1 和和 master 网络不通,daemon 和 master 连接畅通,那此时 mater 节点是否需要 failover 那?

Redis 的 sentinel 提供了一套多 daemon 间的交互机制,多个 daemon 间组成一个集群,成为 sentinel 集群,daemon 节点也称为 sentinel 节点。如下图所示:

这些节点相互间通信、选举、协商,在 master 节点的故障发现、failover 决策上表现出一致性。

sentinel 集群监视任意多个 master 以及 master 下的 slave,自动将下线的 master 从其下的某个 slave 升级为新的 master 代替继续处理命令请求。

启动并初始化 Sentinel

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

./redis-sentinel ../sentinel.conf

或者命令:

./redis-server ../sentinel.conf --sentinel

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

初始化服务器

Sentinel 本质上是运行在特殊模式下的 Redis 服务器,它和普通的 Redis 服务器执行的工作不同,初始化过程也不完全相同。如普通的 Redis 服务器初始化会载入 RDB 或者 AOF 文件来恢复数据,而 Sentinel 启动时不会载入,因为 Sentinel 并不使用数据库。

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

将一部分普通 Redis 服务器使用的代码替换成 Sentinel 专用代码。如普通 Redis 服务器使用 server.c/redisCommandTable 作为服务器的命令表:

truct redisCommand redisCommandTable[] = { { module ,moduleCommand,-2, as ,0,NULL,0,0,0,0,0},
 {get ,getCommand,2, rF ,0,NULL,1,1,1,0,0},
 {set ,setCommand,-3, wm ,0,NULL,1,1,1,0,0},
 {setnx ,setnxCommand,3, wmF ,0,NULL,1,1,1,0,0},
 {setex ,setexCommand,4, wm ,0,NULL,1,1,1,0,0},
 {psetex ,psetexCommand,4, wm ,0,NULL,1,1,1,0,0},
 {append ,appendCommand,3, wm ,0,NULL,1,1,1,0,0},
 .....
 {del ,delCommand,-2, w ,0,NULL,1,-1,1,0,0},
 {unlink ,unlinkCommand,-2, wF ,0,NULL,1,-1,1,0,0},
 {exists ,existsCommand,-2, rF ,0,NULL,1,-1,1,0,0},
 {setbit ,setbitCommand,4, wm ,0,NULL,1,1,1,0,0},
 {getbit ,getbitCommand,3, rF ,0,NULL,1,1,1,0,0},
 {bitfield ,bitfieldCommand,-2, wm ,0,NULL,1,1,1,0,0},
 {setrange ,setrangeCommand,4, wm ,0,NULL,1,1,1,0,0},
 {getrange ,getrangeCommand,4, r ,0,NULL,1,1,1,0,0},
 {substr ,getrangeCommand,4, r ,0,NULL,1,1,1,0,0},
 {incr ,incrCommand,2, wmF ,0,NULL,1,1,1,0,0},
 {decr ,decrCommand,2, wmF ,0,NULL,1,1,1,0,0},
 {mget ,mgetCommand,-2, rF ,0,NULL,1,-1,1,0,0},
 {rpush ,rpushCommand,-3, wmF ,0,NULL,1,1,1,0,0},
 {lpush ,lpushCommand,-3, wmF ,0,NULL,1,1,1,0,0}
 ......
 }

Sentinel 使用 sentinel.c/sentinelcmds 作为服务器列表,如下所示:

struct redisCommand sentinelcmds[] = { { ping ,pingCommand,1, ,0,NULL,0,0,0,0,0},
 {sentinel ,sentinelCommand,-2, ,0,NULL,0,0,0,0,0},
 {subscribe ,subscribeCommand,-2, ,0,NULL,0,0,0,0,0},
 {unsubscribe ,unsubscribeCommand,-1, ,0,NULL,0,0,0,0,0},
 {psubscribe ,psubscribeCommand,-2, ,0,NULL,0,0,0,0,0},
 {punsubscribe ,punsubscribeCommand,-1, ,0,NULL,0,0,0,0,0},
 {publish ,sentinelPublishCommand,3, ,0,NULL,0,0,0,0,0},
 {info ,sentinelInfoCommand,-1, ,0,NULL,0,0,0,0,0},
 {role ,sentinelRoleCommand,1, l ,0,NULL,0,0,0,0,0},
 {client ,clientCommand,-2, rs ,0,NULL,0,0,0,0,0},
 {shutdown ,shutdownCommand,-1, ,0,NULL,0,0,0,0,0},
 {auth ,authCommand,2, sltF ,0,NULL,0,0,0,0,0}
}

初始化 Sentinel 状态

服务器会初始化一个 sentinel.c/sentinelState 结构(保存服务器中所有和 Sentinel 功能有关的状态)。

struct sentinelState {
 
 char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. */
 
 // 当前纪元,用于实现故障转移
 uint64_t current_epoch; /* Current epoch. */
 
 // 监视的主服务器
 // 字典的键是主服务器的名字
 // 字典的值则是一个指向 sentinelRedisInstances 结构的指针
 dict *masters; /* Dictionary of master sentinelRedisInstances.
 Key is the instance name, value is the
 sentinelRedisInstance structure pointer. */
 // 是否进入 tilt 模式
 int tilt; /* Are we in TILT mode? */
 
 // 目前正在执行的脚本数量
 int running_scripts; /* Number of scripts in execution right now. */
 
 // 进入 tilt 模式的时间
 mstime_t tilt_start_time; /* When TITL started. */
 
 // 最后一次执行时间处理器的时间
 mstime_t previous_time; /* Last time we ran the time handler. */
 
 //  一个 FIFO 队列,包含了所有需要执行的用户脚本
 list *scripts_queue; /* Queue of user scripts to execute. */
 
 char *announce_ip; /* IP addr that is gossiped to other sentinels if
 not NULL. */
 int announce_port; /* Port that is gossiped to other sentinels if
 non zero. */
 unsigned long simfailure_flags; /* Failures simulation. */
 int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script
 paths at runtime? */
}

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

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

字典的 key 是监视主服务器的名字,字典的值则是被监控主服务器对应的 sentinel.c/sentinelRedisInstance 结构。

sentinelRedisInstance 结构部分属性如下:

typedef struct sentinelRedisInstance {
 // 标识值,记录了实例的类型,以及该实例的当前状态
 int flags; /* See SRI_... defines */
 
 // 实例的名字
 // 主服务器的名字由用户在配置文件中设置
 // 从服务器以及 Sentinel 的名字由 Sentinel 自动设置
 // 格式为 ip:port, 例如“127.0.0.1:26379” char *name; /* Master name from the point of view of this sentinel. */
 
 // 实例运行的 ID
 char *runid; /* Run ID of this instance, or unique ID if is a Sentinel.*/
 
 // 配置纪元,用于实现故障转移
 uint64_t config_epoch; /* Configuration epoch. */
 
 // 实例的地址
 sentinelAddr *addr; /* Master host. */
 
 //sentinel down-after-milliseconds 选项设定的值
 // 实例无响应多少毫秒之后才会被判断为主观下线(subjectively down) mstime_t down_after_period; /* Consider it down after that period. */
 
 //sentinel monitor  master-name   ip   redis-port   quorum选项中的 quorum
 // 判断这个实例为客观下线(objective down)所需的支持投票的数量
 unsigned int quorum;/* Number of sentinels that need to agree on failure. */ 
 //sentinel parallel-syncs  master-name   numreplicas   选项的 numreplicas 值
 // 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
 int parallel_syncs; /* How many slaves to reconfigure at same time. */
 
 //sentinel failover-timeout  master-name   milliseconds 选项的值
 // 刷新故障迁移状态的最大时限
 mstime_t failover_timeout; /* Max time to refresh failover state. */
}

例如启动 Sentinel 时,配置了如下的配置文件:

sentinel monitor  master-name   ip   redis-port   quorum
sentinel monitor master1 127.0.0.1 6379 2
# sentinel down-after-milliseconds  master-name   milliseconds 
sentinel down-after-milliseconds master1 30000
# sentinel parallel-syncs  master-name   numreplicas 
sentinel parallel-syncs master1 1
# sentinel failover-timeout  master-name   milliseconds 
sentinel failover-timeout master1 900000

则 Sentinel 则会为主服务器 master1 创建如下图所示的实例结构:

Sentinel 状态以及 masters 字典的机构如下:

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

创建连向被监视主服务器的网络连接,Sentinel 将成为主服务器的客户端,向主服务器发送命令并从命令回复获取信息。

Sentinel 会创建两个连向主服务器的异步网络连接:

命令连接,用于向主服务器发送命令并接收命令回复

订阅连接,订阅主服务器的_sentinel_:hello 频道

Sentinel 发送信息和获取信息

Sentinel 默认会以每十秒一次的频率,通过命令连接向被监视的 master 和 slave 发送 INFO 命令。

通过 master 的回复可获取 master 本身信息,包括 run_id 域记录的服务器运行 ID,以及 role 域记录的服务器角色。另外还会获取到 master 下的所有的从服务器信息,包括 slave 的 ip 地址和 port 端口号。Sentinel 无需用户提供从服务器的地址信息,由 master 返回的 slave 的 ip 地址和 port 端口号,可以自动发现 slave。

当 Sentinel 发现 master 有新的 slave 出现时,Sentinel 会为这个新的 slave 创建相应的实例外,Sentinel 还会创建到 slave 的命令连接和订阅连接。

根据 slave 的 INFO 命令的回复,Sentinel 会提取如下信息:

1.slave 的运行 ID run_id

2.slave 的角色 role

3.master 的 ip 地址和 port 端口

4.master 和 slave 的连接状态 master_link_status

5.slave 的优先级 slave_priority

6.slave 的复制偏移量 slave_repl_offset

Sentinel 在默认情况下会以每两秒一次的频率,通过命令连接向所有被监视的 master 和 slave 的_sentinel_:hello 频道发送一条信息

发送以下格式的命令:

 PUBLISH _sentinel_:hello  s_ip , s_port , s_runid , s_epoch , m_name , m_ip , m_port , m_epoch

以上命令相关参数意义:

参数意义 s_ipSentinel 的 ip 地址 s_portSentinel 的端口号 s_runidSentinel 的运行 IDs_runidSentinel 的运行 IDm_name 主服务器的名字 m_ip 主服务器的 IP 地址 m_port 主服务器的端口号 m_epoch 主服务器当前的配置纪元

Sentinel 与 master 或者 slave 建立订阅连接之后,Sentinel 就会通过订阅连接发送对_sentinel_:hello 频道的订阅, 订阅会持续到 Sentinel 与服务器的连接断开为止

命令如下所示:

SUBSCRIBE sentinel:hello

如上图所示,对于每个与 Sentinel 连接的服务器,Sentinel 既可以通过命令连接向服务器频道_sentinel_:hello 频道发送信息,又通过订阅连接从服务器的_sentinel_:hello 频道接收信息。

sentinel 间会相互感知,新加入的 sentinel 会向 master 的_sentinel_:hello 频道发布一条消息,包括自己的消息,其它该频道订阅者 sentinel 会发现新的 sentinel。随后新的 sentinel 和其它 sentinel 会创建长连接。

相互连接的各个 Sentinel 可以进行信息交换。Sentinel 为 master 创建的实例结构中的 sentinels 字典保存了除 Sentinel 本身之外,所有同样监视这个主服务器的其它 Sentinel 信息。

前面也讲到 sentinel 会为 slave 创建实例(在 master 实例的 slaves 字典中)。现在我们也知道通过 sentinel 相互信息交换,也创建了其它 sentinel 的实例(在 master 实例的 sentinels 字典中)。我们将一个 sentinel 中保存的实例结构大概情况理一下,如下图所示:

从上图可以看到 slave 和 sentinel 字典的键由其 ip 地址和 port 端口组成,格式为 ip:port, 其字典的值为其对应的 sentinelRedisInstance 实例。

master 的故障发现

主观不可用

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

PING 命令回复分为下面两种情况:

有效回复:实例返回 +PONG、-LOADING、-MASTERDOWN 三种回复的一种

无效回复:除上面有效回复外的其它回复或者在指定时限内没有任何返回

Sentinel 配置文件中的设置 down-after-milliseconds 毫秒时效内(各个 sentinel 可能配置的不相同),连续向 Sentinel 返回无效回复,那么 sentinel 将此实例置为主观下线状态,在 sentinel 中维护的该实例 flags 属性中打开 SRI_S_DOWN 标识,例如 master 如下所示:

客观不可用

在 sentinel 发现主观不可用状态后,它会将“主观不可用状态”发给其它 sentinel 进行确认,当确认的 sentinel 节点数 =quorum,则判定该 master 为客观不可用,随后进入 failover 流程。

上面说到将主观不可用状态发给其它 sentinel 使用如下命令:

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

各个参数的意义如下:

ip:被 sentinel 判断为主观下线的主服务器的 ip 地址

port: 被 sentinel 判断为主观下线的主服务器的 port 地址

current_epoch:sentinel 的配置纪元,用于选举领头 Sentinel

runid:可以为 * 号或者 Sentinel 的运行 ID,* 号代表检测主服务器客观下线状态。Sentinel 的运行 ID 用于选举领头 Sentinel

接受到以上命令的 sentinel 会反回一条包含三个参数的 Multi Bulk 回复:

1)down_state 目标 sentinel 对该 master 检查结果,1:master 已下线 2:master 未下线

2)leader_runid 两种情况,* 表示仅用于检测 master 下线状态,否则表示局部领头 Sentinel 的运行 ID(选举领头 Sentinel)

3)leader_epoch 当 leader_runid 为时,leader_epoch 始终为 0。不为时则表示目标 Sentinel 的局部领头 Sentinel 的配置纪元(用于选举领头 Sentinel)

其中节点数量限制 quorum 为 sentinel 配置文件中配置的

sentinel monitor  master-name   ip   redis-port   quorum

quorum 选项,不同的 sentinel 配置的可能不相同。

当 sentinel 认为 master 为客观下线状态,则会将 master 属性中的 flags 的 SRI_O_DOWN 标识打开,例如 master 如下图所示:

选举 Sentinel Leader

当一台 master 宕机时,可能多个 sentinel 节点同时发现并通过交互确认相互的“主观不可用状态”,同时达到“客观不可用状态”,同时打算发起 failover。但最终只能有一个 sentinel 节点作为 failover 发起者,那么就需要选举出 Sentinel Leader,需要开始一个 Sentinel Leader 选举过程。

Redis 的 Sentinel 机制采用类似于 Raft 协议实现这个选举算法:

1.sentinelState 的 epoch 变量类似于 raft 协议中的 term(选举回合)。

2. 每一个确认了 master“客观不可用”的 sentinel 节点都会向周围广播自己的参选请求(SENTINEL is-master-down-by-addr ip port current_epoch run_id ,current_epoch 为自己的配置纪元,run_id 为自己的运行 ID)

3. 每一个接收到参选请求的 sentinel 节点如果还没接收到其它参选请求,它就将本回合的意向置为首个参选 sentinel 并回复它(先到先得);如果已经在本回合表过意向了,则拒绝其它参选,并将已有意向回复(如上所介绍的三个参数的 Multi Bulk 回复,down_state 为 1,leader_runid 为首次接收到的发起参选请求的源 sentinel 的运行 ID,leader_epoch 为首次接收到的发起参选请求的源 sentinel 的配置纪元)

4. 每个发起参选请求的 sentinel 节点如果收到超过一半的意向同意某个参选 sentinel(可能是自己),则确定该 sentinel 为 leader。如果本回合持续了足够长时间未选出 leader,则开启下一个回合

leader sentinel 确定之后,leader sentinel 从 master 所有的 slave 中依据一定规则选取一个作为新的 master。

故障转移 failover

在选举出 Sentinel Leader 之后,sentinel leader 对已下线 master 执行故障转移:

sentinel leader 对已下线的 master 的所有 slave 中,选出一个状态良好、数据完整的 slave,然后向这个 slave 发送:SLAVEOF no one 命令,将这个 slave 转换为 master。

我们来看下新的 master 是怎么挑选出来的?Sentinel leader 会将已下线的所有 slave 保存到一个列表,然后按照以下规则过滤筛选:

优先级最高的 slave,redis.conf 配置中 replica-priority 选项来标识,默认为 100,replica-priority 较低的优先级越高。0 为特殊优先级,标志为不能升级为 master。

如果存在多个优先级相等的 slave,则会选择复制偏移量 (offset) 最大的 slave(数据更加完整)

如果存在多个优先级相等,最大复制偏移量最大的 slave,则选择运行 ID 最小的 slave

选出需要升级为新的 master 的 slave 后,Sentinel Leader 会向这个 slave 发送 SLAVEOF no one 命令。之后 Sentinel 会以每秒一次频率 (平时是十秒一次) 向被升级 slave 发送 INFO,当回复的 role 由 slave 变为 master 时 Sentinel Leader 就会知道已升级为 master。

sentinel leader 向已下线的 master 属下的 slave 发送 SLAVEOF 命令(SLAVEOF new_master_ip new_master_port),去复制新的 master。

将旧的 master 设置为新的 master 的 slave,并继续对其监视,当其重新上线时 Sentinel 会执行命令让其成为新的 master 的 slave。

以上是“Redis 中 sentinel 故障转移的示例分析”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注丸趣 TV 行业资讯频道!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-27发表,共计10206字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)