Redis设计与实现哨兵模式原理

编程

上一篇:Redis设计与实现-主从复制原理

主从复制可以提高数据库的可用性和性能,但一旦主服务器挂掉,整个服务依然无法使用,因此Redis设计了哨兵模式

概要

哨兵模式是主从模式的升级版,通过多个哨兵检测任意多个主从服务器,当有主服务器挂掉的时候,首先确定一个主事的哨兵,然后由该哨兵来主持从掉线主服务器下的从服务器列表中选一个从服务器作为主服务器并完成主从切换操作

三个哨兵共同监视着三个主节点(Master),假设第一个主节点挂掉,那么会从三个Sentinel中选择一个作为主事(故障转移)的Sentinel,由该Sentinel在第一个主节点的下的从节点列表中选择最优的作为新的主节点。

下面对哨兵模式如何具体工作的做介绍。

在介绍之前先思考几个问题

  1. Sentinel是个什么样的进程,和普通的RedisServer的区别在哪里

  2. Sentinel既然要监视主从,它是怎么得到主从服务器的相关信息,并如何记录的

  3. Sentinel它们之间应该会通信吧

  4. 有多个Sentinel,它们如何协同工作完成主从节点切换,并让整个哨兵模式的所有服务器状态达成一致

Sentinel初始化

和redisServer相似,因为它和数据库无关,所以它文件事件和时间事件都是和哨兵相关联的。程序启动的时候会执行相应的Sentinel特有的代码等完成Sentinel的初始化工作。既然说到了初始化,那么一定要先看它的数据结构,以及对于哨兵的配置文件

typedef struct sentinelState{

//当前纪元(整数值),表示所有哨兵在同一事件周期内,如:选举主事的哨兵需要在一个配置纪元内才能保证过半原则的唯一性

uint64_t current_epoch;

//所有被该哨兵监视的主服务器

//Key:主服务器名

//Value:一个指针(sentinelRedisInstance:主服务相关信息)

dict *masters;

//是否进入了tilt模式,关于tilt模式参考文章比较靠后的内容

int tilt;

//目前正在执行脚本数量

int running_scripts;

//进入tilt模式的时间

mstime_t tilt_start_time;

//最后一次执行时间处理器的时间

mstime_t previous_time;

//一个fifo队列,包含所有需要执行的用户脚本

list *scripts_queue

}

typedef struct sentinelRedisInstance{

//标示值,记录了实例的类型,以及该实例的当前状态

int flags;

//实例的名字-主服务器:配置文件中的,从服务器和哨兵由程序自动生成

//格式:ip:port

char *name;

//从服务器字典

dict slaves;

//监视该主服务器的哨兵字典

dict sentinel;

//实例的run id

char *runid;

//纪元,用于实现故障转移

uint64_t config_epoch;

//实例的地址

sentinelAddr * addr;

//SENTINEL down-after-milliseconds选项的值

//实例无响应多少毫秒之后被判定为主观下线

mstime_t down_after_period

//SENTINEL monitor <master-name><ip><port><quorum>选项中的quorum参数

//判断这个实例客观下线所需的支持投票数量

int quorum;

//SENTINEL parallel-syncs <master-name><number>选项值

//在执行故障转移时,可以同时对新的主服务器进行同步的从服务器数量

int parallel_syncs;

//SENTINEL failover-timeout <master-name><ms>选项值

//故障转移状态的最大时间

mstime_t failover_timeout;

}

所有配置最终也会落地到数据结构中,从上面的数据结构定义中可以看到有如下几个典型的配置

#比较重要的调优参数

SENTINEL down-after-milliseconds

SENTINEL monitor <master-name><ip><port><quorum>

SENTINEL parallel-syncs <master-name><number>

SENTINEL failover-timeout <master-name><ms>

#当然还有一些日常配置项,比如Sentinel端口、日志文件、认证等

既然在配置文件中配置了主服务器的IP端口等信息,那么在初始化Sentinel的时候就会拿它创建命令连接和订阅连接

  • 命令连接:向主服务器发送命令,接收命令的回复,执行ping命令检测主服务器存活状态

  • 订阅连接:订阅主服务器的sentinel:hello频道,向其他Sentinel广播自己是某一个主服务器的Sentinel,让其他Sentinel知道它也监视了该主服务器的

获取主服务器

Sentinel使用命令连接每隔10秒向主服务器发送info命令获取主服务器的当前信息,通过info可以获取一下几项内容

  • 主服务器的run id以及role服务器角色(master)信息

  • 主服务器中的从服务器信息:slave0:ip=192.168.10.22,port=6379,state=online,offset=89699,lag=1。拿到该信息会在sentinelState.sentinelRedisInstance.slaves中新增或者更新从服务器数据结构,这样Sentinel就有了主服务器以及该主服务器下的所有从服务器信息

获取到主服务器相关信息后完成主从服务器数据结构填充,然后创建从服务器的命令和订阅连接。

获取从服务器

目的在于发现从服务器变化,及时更新主服务对应sentinelState.masters.slaves字典。

如果是新增了从服务器,首先更新sentinelState.masters.slaves,再为从服务器创建命令与订阅连接。

使用发布-订阅更新Sentinel字典

以每2秒钟的频率向所有被监视的主服务器发送如下格式的命令:

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

所有的Sentinel都会使用SUBSRIBE sentinel:hello订阅所有哨兵发布的消息。

因此当某一个Sentinel执行PUSHLISH命令时,其他Sentinel都会接受到订阅频道的消息,然后做更新sentinel的操作,具体步骤:

1.服务器接收到信息,判断是不是自己发送的消息,如果是丢弃

2.如果不是自己,则更新主服务器实例结构中的哨兵字典

通过上面的步骤,监视同一MASTER-A的所有Sentinel对应MASTER-A的sentinelState.masters.sentinel中的内容都是一致的。

创建连向其他Sentinel的连接

Sentinel除了与主从服务器有连接外,还与其他Sentinel创建了命令连接,请注意并没有订阅连接(Sentinel又不是数据库拿来的发布/订阅)

说到这里心里想,某一个Sentinel是如何知道其他Sentinel的存在的喃?答案就是上面提供的发布订阅模式,因此Sentinel只知道所有Sentinel当中与它监视相同主服务器Sentinel,而不是所有。

 

重要:到这里Sentinel与主从服务器创建了命令连接,与主服务器相关的Sentinel也创建了命令连接,接下来就可以使用这些命令连接与服务器通信完成服务器状态的检测,然后由Sentinel们共同决定并完成故障转移

故障检测与转移

检测

Sentinel确定某一个主服务器是否确实掉线,是由各个相关的Sentinel先探测到某个主服务器下线(主观下线),然后再询问其他Sentinel该主服务器是否下线,如果有一半的Sentinel都说该服务下线,则最终判定该主服务器掉线了(客观下线)。

检测主观下线

Sentinel每1秒一次向所有主、从、Sentinel发送ping命令,如果在down-after-millisecond毫秒之内返回的都是无效信息,则修改对应实例(sentinelRedisInstance)的flags标志位SRI-S-DOWN(主观下线)。

down-after-millisecond不仅作用与主服务器,还作用与该主服务器下的从服务器,以及监视该主服务器的sentinel下线的标准

不同哨兵配置文件中的down-after-millisecond配置不同,因此他们认定主观下线的时间也不一样

检测客观下线

当sentinel在主服务器变成主观下线之后,会询问所有监视该主服务器的sentinel,如果其他sentinel返回主服务器已下线的数量超过quorum,则将主服务器的flag设置为SRI-O-DOWN(客观下线)

既然是根据配置的quorum判断是否过半,因此为了达到真正的过半一定要设置正确,设置该值不要过小或过大,设置为过半多一些即可。

选举领头Sentinel

俗话说一山不容二虎,总得有一个主事的、领头的。选举领头Sentinel目的是让它来主持故障转移,最后告知其他Sentinel主从切换的结果,更新对应实例结构。

选举步骤:

  • 首先所有sentinel都具有被选举为领头的资格

  • 每次选举之后,无论成败,配置纪元epoch都会加1

  • 在一次选举中,所有Sentinel都有一次机会将某个Sentinel设置为局部领头的机会

  • 每个发现主服务器客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头

  • 目标Sentinel会根据先到先得的原则设置局部领头

  • 目标Sentinel在接收请求之后都会返回局部领头RUN ID和配置纪元

  • 源Sentinel接收到回复之后判断是不是一个配置纪元,如果是再判断RUN ID是不是自己,如果是,则票数+1

  • 以上规则保证了不会同时出现两个过半的情况发生,所以如果出现票数过半的情况,则领头出现

  • 如果在规定时间内没有选举成功,配置纪元+1,进行下一次选举

简单的说就是:班上所有的人都有资格竞选班长,每个人只能投一票,且每个人必须要参与班长竞选,获得投票的方式是:谁(候选人)先告诉投票人,那么投票人就投它,并且候选人记住已得票数,当票数过半班长选出来了,然后班长大声说我就是班长,班上所有人都知道了他是班长。

转移故障

当领头Sentinel选举出来之后就由它指导并完成故障转移,步骤:

  • 在从服务器列表中选择一个最优的作为主服务器

  • 所有从服务器从新的主服务器中复制数据

  • 将下线的主服务器设置为新主服务器的从服务器,一旦上线则进行复制操作

选择最后的从服务器作为主服务器

那么如何选择出最优的一个从服务器作为主服务器喃,步骤如下:

  • 删除列表中所有处于下线或者断线状态的从服务器,这可以保证列表中剩余的从服务器都是正常在线的

  • 删除列表中所有最近5秒内都没回复过领头Sentinal的info命令的从服务器,这可以保证列表中剩余的从服务器都是最近成功进行过通信的

  • 删除所有与已下线主服务器连接断开超过down-after-milliseconds *10的从服务器

  • 根据从服务器的优先级进行排序,选出优先级最高的服务器

  • 如果优先级相同的有多个,那么选择出具有最大复制偏移量的从服务器

  • 如果具有多个优先级相同,复制偏移量相同的从服务器,则根据RUN ID排序,选出ID最小的从服务器

总结

哨兵模式相比主从复制,整个系统的可用性大大提高了,可以通过哨兵监控多个集群。不过哨兵模式在主服务器掉线和故障转移过程中服务器的写操作都是不可用的。这对于大企业的高并发、高可用场景是不行了。因此Redis后来又搞了Cluster集群模式。

以上是 Redis设计与实现哨兵模式原理 的全部内容, 来源链接: utcz.com/z/512644.html

回到顶部