Redis 高可用方案:哨兵(Sentinel)机制详解

概述

上一篇文章中我们讲到了Redis的主从复制机制。在实际应用中,单纯采用主从复制模型有以下缺点:

  • 默认配置下,master节点可以进行读和写操作,slave节点只能进行读操作,写操作被禁止(因为即使在slave上进行写操作也无法同步到master节点)。所以,主从复制模型在写操作比较频繁的使用场景下是不大合适的。
  • 一旦master节点宕机后,整个Redis集群就无法对外提供写操作了,因为在主从复制模型下无法进行master的选举。

针对第一个问题,我们可以采用Redis的集群,Redis集群我们下一篇文章再进行讲解。针对第二个问题,我们就可以采用Redis的哨兵(Sentinel)功能了。

Sentinel是Redis高可用性的解决方案,由一个或多个Sentinel节点组成的系统可以监视多个master服务器和属下所有的slave服务器,Sentinel可以在master节点宕机时,选举一个slave充当新的master继续对外服务。

在这里插入图片描述

Sentinel启动

可以通过以下命令启动Sentinel

[root@localhost]# redis-sentinel sentinel.conf

或者:

[root@localhost]# redis-server sentinel.conf --sentinel

Sentinel内部启动步骤和一般的Redis服务器有所不同,它本质上是一个运行在特殊模式下的Redis服务器。在Sentinel初始化服务器后,会将普通Redis服务器使用代码替换为Sentinel专用代码,并通过上述给定的配置文件,与监视的服务器取得连接。

Sentinel服务器在初始化时会创建一个sentinel.c/sentinelState结构体,它保存了服务器中所有和Sentinel功能有关的状态:

struct sentinelState {
	uint64_t current_epoch;    //当前纪元
	dict *masters;			   //一个字典,保存了当前Sentinel所监视的服务器
	int tilt;				   //是否进入了TILT模式
	int running_scripts;	   //目前正在执行的脚本数量
	mstime_t tilt_start_time;  //进入TITL模式的时间
	mstime_t previous_time;	   //最后一次执行时间处理器的时间
	list *scripts_queue;	   //队列,包含所有需要执行的用户脚本
} sentinel;

上述结构体中最重要的就是masters字典,字典的键是服务器的名称,值是一个指向sentinelRedisInstance结构的指针。每个sentinelRedisInstance结构代表了一个Redis服务器节点,这个节点可以为主服务器、从服务器,也可以是Sentinel
sentinelRedisInstance结构源码如下:

typedef struct sentinelRedisInstance {
	int flags;	  //记录节点类型及节点状态
	char *name;	  //节点的名称
	char *runid;  //节点的运行ID
	uint64_t config_epoch; //配置纪元,以实现故障转移
	sentinelAddr *addr; //节点的地址,持有节点的主机名和端口
	mstime_t down_after_period; //节点没有响应后多少毫秒才会被判定为主观下线
	int quorum; //判断这个节点客观下线所需投票数量
	//...
} sentinelRedisInstance;

Sentinel最后一个启动步骤是创建连向被监视的主服务器的网络连接,这里包含了两个连接:

  • 命令连接,Sentinel通过该连接向主服务器发送命令,并接收命令回复。
  • 订阅连接,用来订阅监视服务器的__sentinel__:hello频道。

获取服务器信息

Sentinel默认会以每10秒一次的频率向服务器,向被监视的服务器发送INFO命令,服务器收到INFO命令后会将自己的运行信息发送给Sentinel。运行信息一般包含了以下内容:

  • 该服务器本身的信息:例如run_id
  • 该服务器持有的从服务器信息

如果发现了这个服务器有着新的从服务器出现,Sentinel除了为这个新的从服务器创建相应的结构外,还会和这个从服务器建立命令连接和订阅连接。

除此之外,Sentinel还会以每2秒一次的频率,通过命令连接向所以被监视的服务器的__sentinel__:hello频道发送消息,每条消息包含:

  • Sentinel本身的run_id、IP地址、端口号、当前配置纪元epoch
  • 主服务器的名称、IP地址、端口号、当前配置纪元epoch

对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的消息会被其它 Sentinel接收到,这些消息会被用于其他Sentinel获取该Sentinel的情况,也可用于获取该Sentinel监视的服务器的大致信息。
Sentinel__sentinel__:hello频道的订阅会一直持续到Sentinel与服务器连接断开为止。

Sentinel之间的通信

每个Sentinel服务器都保存了同样监视这个服务器的其它Sentinel的信息,这些Sentinel的信息通过本机的sentinels字典保存。sentinels字典的键是这个Sentinel的IP地址+端口号拼接起来的字符串,键则是这个Sentinel的详细信息结构。

前面提到过,Sentinel通过频道__sentinel__:hello来感知其它Sentinel的存在。每当Sentinel从频道中收到消息时,会进行以下操作:

  • 如果源Sentinel的实例结构在本机已经存在,那么对这个实例结构进行更新。
  • 如果源Sentinel的实例结构不存在,那么说明源Sentinel刚刚开始监视这个服务器,那么本机会给这个源Sentinel创建一个新的实例结构,并将这个结构添加到sentinels字典。并且,本机Sentinel还会和这个源Sentinel创建一个命令连接(并不会创建订阅连接)。

检查服务器下线

Sentinel有两种方式来检验服务器是否处于下线状态,即通过主观判定下线客观判定下线
Sentinel会以每秒一次的频率向所有和它创建了命令连接的服务器(包括普通的Redis服务器和其它Sentinel)发送PING命令,并通过其回复来判断这个服务器是否处于在线状态。

PING命令的回复分为两种情况

  • 有效回复:服务器返回+PONG-LOADING-MASTERDOWN三种回复中的一种
  • 无效回复:服务器返回上述之外的回复,或是在规定时间内没有任何回复。

Sentinel的配置文件中down-after-milliseconds选项指定了Sentinel判断服务器进入主观下线所需的时间长度,单位为毫秒。如果在这个时间段内目标服务器连续向当前Sentinel返回无效回复,那么Sentinel会认定这个服务器进入主观下线状态。

Sentinel判定一个服务器进入主观下线后,为了确认这个服务器是否真的属于下线状态,它会向同样监视这个服务器的其它Sentinel进行询问,这个命令是:

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

Sentinel服务器收到上述命令后,会根据上述命令持有的服务器信息检查主服务器是否下线,然后向源Sentinel返回一条包含三个参数的Multi Bulk回复,这个回复包含以下信息:

  • down_state:目标Sentinel对该服务器的检查结果,1代表服务器已经下线,0代表服务器没有下线。
  • leader_runid:可以为*符号或者目标Sentinel的局部Leader Sentinel的运行ID。*符号表示仅仅用于检查服务器下线状态,局部Leader Sentinel的运行ID用于选举Leader Sentinel服务器。
  • leader_epoch:目标SentinelLeader Sentinel的配置纪元,用于选举Leader Sentinel,仅在leader_runid不为*时有效。

根据其他Sentinel的回复,本机Sentinel将统计同意该服务器已经下线的数量,当达到了客观判定下线的数量时,Sentienl就会将该服务器的结构标记为客观下线状态。

Sentinel选举Leader

当一个主服务器被判断为主观下线时,监视这个下线主服务器的各个Sentinel会进行协商并选举一个Leader,由Leader对下线的服务器进行故障转移操作。

所有在线的Sentinel服务器都有被选举为Leader的资格,每次在进行Leader选举之后,不论其选举是否成功,所有Sentinel的配置纪元的值都会增加。并且Leader一旦设置,在这个配置纪元中就不可修改。

具体的选举流程可以参考这篇博客

故障转移

在选举出Leader Sentinel后,Leader将会对已经下线的主服务器实行故障转移操作,包含以下几个步骤:

  1. 在已经下线的主服务器属下的所有从服务器中,选出一个从服务器转换为主服务器
  2. 已经下线的主服务器属下所有从服务器改为复制新的主服务器
  3. 将已下线的主服务器改为新的从服务器,当这个服务器上线时成为从服务器。

那么如何选举新的主服务器呢?

  1. 首先在从服务器中挑选一个状态良好的从服务器,然后向这个服务器发送SLAVEOF no one命令,将这个从服务器转换为主服务器。
  2. 发送命令后,Leader Sentinel会以每秒一次的频率向这个被升级的从服务器发送INFO命令,并观察命令回复中的Role信息,当Role信息显示为master时,Leader就知道该服务器已经顺利升级成了主服务器。

新的主服务器升级完成后,Leader会让原主服务器属下所有的从服务器(不包含新的主服务器)去复制新的主服务器,也就是通过发送我们熟知的SLAVEOF命令。最后,Leader会将已下线的旧主服务器设为新的主服务器的从服务器,待服务器重新上线后,Sentinel会立刻向它发送SLAVEOF命令

参考资料

《Redis设计与实现》黄建宏著

发布了117 篇原创文章 · 获赞 96 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/abc123lzf/article/details/92018259