什么是哨兵模式
Sentinel(哨兵模式)是Redis的高可用性解决方案:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
切换的大致流程为:
- 当主服务器下线时,Sentinel系统会挑选主服务器属下的一个从服务器,并将这个被选中的从服务器升级为新的主服务器。
- 之后,Sentinel系统会向原来主服务器属下的所有从服务器发送新的复制指令,让它们称为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕。
- 另外,Sentinel还会继续监视已经下线的原主服务器,并在它重新上线时,将它设置为新主服务器的从服务器。
什么是哨兵
当一个Sentinel启动时,它需要执行以下步骤:
- 初始化服务器
- 将普通Redis服务器使用的代码替换成Sentinel专用代码
- 初始化Sentinel状态
- 根据给定的配置文件,初始化Sentinel的监视主服务器列表
- 创建连向主服务器的网络连接
初始化服务器
Sentinel本质上是一个运行在特殊模式下的Redis服务器,所以启动Sentinel的第一步就是初始化一个普通的Redis服务器,但是因为二者需要执行的工作不同,所以初始化过程并不完全相同,比如Sentinel并不使用数据库,所以初始化Sentinel时就不会载入RDB或者AOF文件。
使用Sentinel专用代码
启动Sentinel的第二个步骤就是将一部分普通的Redis服务器使用的代码替换成Sentinel专用代码。比如说,普通Redis服务器使用 redis.h/REDIS_SERVERPORT 常量的值作为服务器端口,而Sentinel则使用 sentinel.c/REDIS_SENTINEL_PORT 常量的值作为服务器端口。
初始化Sentinel状态
在应用了Sentinel的专用代码之后,服务器会初始化一个 sentinel.c/sentinelState 结构,这个结构保存了服务器中所有和Sentinel功能有关的状态。
初始化Sentinel状态的masters属性
Sentinel状态中的masters字典记录了所有被Sentinel监视的主服务器的相关信息,其中:
- 字典的键是被监视主服务器的名字
- 字典的值是被监视主服务器对应的 sentinel.c/sentinelRedisInstance结构,每个实例结构代表一个被Sentinel监视的Redis服务器实例,这个实例可以是主服务器、从服务器或者另一个Sentinel。
创建连向主服务器的网络连接
初始化Sentinel的最后一步是创建连向被监视主服务器的网络连接,Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关的信息。
对于每个被Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接:
- 一个是命令连接,这个连接专门用于向主服务器发送命令,并接收命令回复
- 另一个是订阅连接,这个连接专门用于订阅主服务器的 _sentinel _ :hello 频道
获取主服务器信息
Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送 INFO 命令,并通过分析 INFO 命令的回复来获取主服务器的当前信息:
- 一方面是关于主服务器本身的信息,包括 run_id 域记录的服务器运行ID,以及 role 域记录的服务器角色;
- 另一方面是关于主服务器属下所有从服务器的信息,每个从服务器都由一个 “salve” 字符串开头的行记录,每行的 IP= 域记录了从服务器的IP地址,而 port = 域则记录了从服务器的端口号。根据这些IP和端口号,Sentinel无须用户提供从服务器的地址信息,就可以自动发现从服务器。
获取从服务器信息
当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实例结构之外,Sentinel还会创建连接到从服务器的命令连接和订阅连接。
向服务器发送信息
在默认情况下,Sentinel会以每两秒一次的频率,通过命令连接向所有被监视的服务器发送命令,其中包括了和Sentinel与主服务器有关的参数。
参数 |
意义 |
s_ip |
Sentinel的IP地址 |
s_port |
Sentinel的端口号 |
s-runid |
Sentinel的运行ID |
s-epoch |
Sentinel当前的配置纪元 |
m-name |
主服务器名字 |
m-ip |
主服务器的IP地址 |
m-port |
主服务器的端口号 |
m-epoch |
主服务器当前的配置纪元 |
接收来自主服务器和从服务器的频道信息
当Sentinel建立起订阅连接之后,就会通过订阅连接,不断的向服务器发送命令直到连接断开,对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他Sentinel接收到,用于更新对发送信息Sentinel和被监视服务器的认知。
更新sentinels字典
Sentinel为主服务器创建的实例结构中的sentinels字典保存了出本身之外,所有同样监视这个主服务器的其他Sentinel的资料。
创建连向其他Sentinel命令的连接
当Sentinel通过频道信息发现一个新的Sentinel时,不仅会为这个Sentinel创建对应的实例结构,还会创建一个连向新的Sentinel的命令连接
检测主观下线状态
在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(包括主从服务器和其他Sentinel)发送PING命令,并通过实例返回的PING命令回复来判断实例是否在线。
- 有效回复:实例返回+PONG、-LOADING、-MASTERDOWN三种回复中的一种
- 无效回复:实例返回除+PONG、-LOADING、-MASTERDOWN三种回复之外的其他回复,或者在指定时限内没有返回任何回复
如果主服务器连续在Sentinel设定的时间内都返回了无效回复,那么Sentinel就会将主服务器标记为主观下线,该时间取决于down-after-milliseconds选项的值。
检查客观下线状态
当一个Sentinel判断主服务器主观下线之后,为了确定主服务器是否真的下线,它会向监视这个主服务器的其他Sentinel发起询问,当Sentinel从其他Sentinel那里接收到足够数量的已下线判断之后,Sentinel就会将当前服务器判定为客观下线,开始执行故障转移操作。
发送SENTINEL is - master - down -by - addr命令
接收SENTINEL is - master - down - by - addr命令
当一个目标Sentinel接收到另一个源Sentinel发送来的命令之后,目标Sentinel会根据信息检查主服务器是否已下线,然后向源Sentinel发送回复信息
接收SENTINEL is-master-down-by-addr命令的回复
根据其他Sentinel发回的命令回复,Sentinel会统计其他Sentinel同意主服务器已下线的数量,当这一数量达到配置指定的判断客观下线所需的数量时,Sentinel会标记主服务器已经进入客观下线状态。
判断条件
- 当认为主服务器已经进入下线状态的Sentinel的数量,超过了Sentinel配置中设置的quorum参数的值,那么该Sentinel就会认为主服务器已经进入客观下线状态。
- 不同Sentinel判断客观下线的条件可能不同
选举领头Sentinel
当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel就会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器进行故障转移操作。
选举规则和方法:
- 1、所有在线的Sentinel都有被选为领头Sentinel的资格,换句话说,监视同一个主服务器的多个在线Sentinel中的任意一个都有可能称为领头Sentinel
- 2、每次进行领头Sentinel选举之后,不论选举是否成功,所有Sentinel的配置纪元的值都会自增一次。配置纪元实际上就是一个计数器,并没有什么特别的。
- 3、在一个配置纪元里面,所有Sentinel都有一次将某个Sentinel设置为局部领头Sentinel的机会,并且局部领头一旦设置,在这个配置纪元里面就不能再更改。
- 4、每个发现主服务器进入客观下线的Sentinel都会要求其他的Sentinel将自己设置为局部领头Sentinel。
- 5、当一个Sentinel(源Sentinel)向另一个Sentinel(目标Sentinel)发送SENTINEL is-master-down-by-addr命令,并且命令中的runid参数不是*符号而是源sentinel的运行ID时,这表示源Sentinel要求目标Sentinel将前者设置为后者的局部领头Sentinel。
- 6、Sentinel设置局部领头Sentinel的规则是先到先得:最先向目标Sentinel发送设置要求的源Sentinel将成为目标Sentinel的局部领头Sentinel,而之后接收到的所有设置要求都会被目标Sentinel拒绝。
- 7、目标Sentinel在接受到命令之后,将会向源Sentinel返回一条命令回复,回复中的leader_runid参数和leader_epoch参数分别记录了目标Sentinel的局部领头Sentinel的运行ID和配置纪元。
- 8、源Sentinel在接收到目标Sentinel返回的命令回复之后,会检查回复中的leade_epoch参数的值和自己的配置纪元是否相同,如果相同的话,那么源Sentinel继续取出回复中的leader_runid参数,如果leader_runid参数的值和源Sentinel的运行ID一致,那么表示目标Sentinel将源Sentinel设置成了局部领头Sentinel。
- 9、如果有某个Sentinel被半数以上的Sentinel设置成了局部领头Sentinel,那么这个Sentinel称为领头Sentinel。
- 10、因为领头Sentinel需要获得半数以上Sentinel的支持,并且每个Sentinel在每个配置纪元里面只能设置一次局部领头Sentinel,所以在一个配置纪元里面,只会出现一个领头Sentinel。
- 11、如果在给定时限内,没有一个Sentinel被选举为领头Sentinel,那么各个Sentinel将在一段时间之内再次进行选举,知道选举出领头Sentinel为止。
故障转移
在选举出领头Sentinel之后,领头Sentinel将对已下线的主服务器执行故障转移操作,包含以下步骤:
- 在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器。
- 让已下线主服务器属下的所有从服务器改为复制新的主服务器。
- 将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。
选出新的主服务器
故障转移操作第一步要做的就是在已下线主服务器的所有从服务器中挑选出一个状态良好、数据完整的从服务器作为新的主服务器。
挑选过程:
领头Sentinel会将已下线主服务器的所有从服务器保存到一个列表里面,然后不断进行过滤:
- 1、删除列表中所有处于下线或者断线状态的从服务器,保证列表中剩余的服务器都是正常在线的。
- 2、删除列表中所有最近五秒内没有回复过领头Sentinel的INFO命令的从服务器,这可以保证列表中剩余的从服务器都是最近成功进行过通信的。
- 3、删除所有与已下线主服务器连接断开超过down-after-milliseconds * 10毫秒的服务器,保证剩余服务器的数据都是比较新的。
之后,领头Sentinel将根据从服务器的优先级,对列表中剩余的从服务器进行排序,并选出其中优先级最高的从服务器,如果具有多个优先级相同的从服务器,那么领头Sentinel将会按照从服务器的复制偏移量进行排序,选择其中偏移量最大的从服务器,如果复制偏移量也相同,则会按照运行ID进行排序,选择运行ID最小的从服务器作为新的主服务器。
修改从服务器的复制目标
当新的主服务器出现之后,领头Sentinel下一步的工作就是让已下线的主服务器属下的所有从服务器去复制新的主服务器,这一动作可以通过向从服务器发送SlAVEOF命令来实现。
将旧的主服务器变为从服务器
故障转移操作最后要做的就是将已下线的主服务器设置为新的主服务器的从服务器。