刚好紧接上一章 的主从架构,我们这次来分享另外一种 原生高可用方案,就是
sentinel 哨兵
架构
主从架构优势比较明显,可以 保障数据的高可用,从服务器可以扩展主服务器的读能力,有效应对大并发操作
但是,如果主服务器发生故障的时候,需要手动将一个从服务器升级为主服务器,同时需要通知业务方变更配置,并且需要让其它从服务器去复制新主服务器节点,整个过程需要人为干预,比较繁琐;另外还有一个就是,如果没有升级 Redis 到 2.8 以上,则 可能因为 全量同步问题 造成大量资源消耗
所以 Redis 社区推出了一种原生的高可用解决方案,就是 Redis sentinel 架构,由一个或多个 sentinel 实例 组成的 sentinel 系统可以监控任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监控的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务,实现 Redis 架构的 监控、通知 以及 自动故障转移
由于我们这篇文章主要着重 源码解析,所以关于 Redis sentinel 哨兵架构的部署 诸位可以自行查找一些资料进行实验,我们假定 大家都已经能熟练使用 Redis sentinel 架构
哨兵 sentinel 的使命
常见的 Redis sentinel 架构如下:
由上图可见,主要由 sentinel 集群 和 Redis 数据服务器 组成,sentinel 集群对 所有 Redis 数据服务器进行监控,同时 sentinel 集群中 各个哨兵 服务器又相互监控,保证 哨兵监控集群的可用;不过一般 一个稳健的 Redis Sentinel 集群,应该使用至少 三个 Sentinel 实例,并且保证讲这些实例放到 不同的机器 上,甚至不同的 物理区域
Redis sentinel 哨兵的职责主要在于 主服务器存活检测
、主从运行状态监控
、自动故障转移
、主从切换
等,针对 Redis 数据服务器 的功能主要有如下几点:
监控
不断定期检查 主从数据服务器 的存活状况以及运行状态通知
如果 监控目标服务器出现问题,通过 API 向 管理员 或者 其他应用程序 发送通知自动故障转移
如果主服务器 下线而不能正常工作的时候,sentinel 会开始执行自动故障转移 操作,从 下线主服务器 下属的 从服务器中 选举出一个作为 主服务器,并且将 其他 从服务器 指向 新的主服务器;不过 下线判断 也分情况,默认 sentinel 会对其监控的 其他 sentinel 节点 和 数据服务器节点 进行每秒一次的 PING 命令检测主观下线 SDOWN
如果 监控的服务器节点 在down-after-milliseconds
设置的毫秒时效内没有响应检测,则会被判定为 主观下线;这个状态适用所有服务器节点客观下线 ODOWN
主服务器节点在发生故障的时候,sentinel 会通过is-master-down-by-addr
命令向其他 sentinel 节点询问 该主服务器节点的状态,如果超过quorum
个数的哨兵节点都认为 主服务器节点不可达,则判定为 客观下线;这个状态只会针对 主服务器节点
配置提供者
在 Redis Sentinel 结构中,客户端在初始化的时候连接的是Sentinel 节点集合,从中获取主节点信息
监控通讯
通讯命令
- sentinel 节点 和 redis 数据节点 之间
PING
sentinel 向 redis 数据节点发送 PING 命令,检查其状态INFO
sentinel 向 redis 数据节点的 主服务器节点发送 INFO 命令,获取 其他从服务器节点信息PUBLISH
sentinel 向 redis 数据节点__sentinel__:hello
频道 发布自己的信息及主服务器节点相关的配置SUBSCRIBE
sentinel 通过订阅 redis 主从服务器节点 的__sentinel__:hello
频道,获取正在监控相同服务的其他 sentinel 节点信息
- sentinel 节点 和 sentinel 节点 之间
PING
sentinel 向其他 sentinel节点 发送 PING 命令,检查节点状态is-master-down-by-addr
和其他 sentinel 协商 主服务器节点 的状态,如果 主服务器 处于 SDOWN 状态,则投票自动选出新的 主服务器节点
交互过程
每个 sentinel 以 每秒一次 的频率,向它所知的 主服务器节点 和 从服务器节点 以及其他 sentinel 节点 发送 PING 命令,检测其状态
如果一个 节点 或者 实例 距离 最后一次 有效回复 PING 命令的时间超过 down-after-milliseconds
设定的值,那么这个 节点 或者 实例会被 sentinel 标记为 SDOWN 主观下线
随后正在监控这个 主服务器节点 的所有 sentinel 都要以每秒一次的频率确认这个 主服务器节点 是否真正进入了 主观下线 状态,一旦有 足够数量 的 sentinel 在指定时间范围内同意这个下线判断,那么这个 服务器就要被标记为 ODOWN 客观下线
,然后 sentinel 向 客观下线主服务器 的所有 从服务器 发送 INFO 命令的频率,会从 10 秒一次改为 每秒一次
当然,如果 中途当没有足够数量的 sentinel 同意下线判断,那这个 客观下线 状态就会被移除,而且当 主服务器重新对 PING 进行有效响应的时候,那 主观下线状态就会被移除
接上面,如果还是 客观下线状态,就需要进行 主服务器 选举逻辑;slave 的选举主要会评估这几个方面
- 与master断开连接的次数
- slave的优先级
- 数据复制的下标( 用来评估 slave 当前拥有多少 master 的数据 )
- 进程ID
源码解析
由于有些方法里的代码量比较大,我们这里按照 典型的代码片段进行解析,同志们可以根据文章提示的代码位置 和 代码里面的关键词 在源码中搜素,可能数据结构一些元素 看不太懂什么意思,没关系,先混个脸熟,后面看完回头再看过来就明白了
启动
sentinel 在源码角度就是一个 特殊模式运行的 Redis 服务器,入口执行的依然是 server.c
的 main()
函数
通过 initSentinelConfig()
、initSentinel()
两个函数进行初始化系列处理,不过 initSentinelConfig()函数 实际就是设置初始化 sentinel 端口配置,我们下面着重看一下 initSentinel() 函数
刚才上面也发现了关于 配置文件载入的逻辑,调试后发现 sentinel 配置解析 主要调用的还是 sentinelHandleConfiguration()
函数
上面我们经常会提到一个创建实例函数 createSentinelRedisInstance()
,其中有会传递用来表示实例类型的参数 flags ( SRI_MASTER | SRI_SLAVE | SRI_SENTINEL )
;实际这个函数主要就是初始化这个创建的实例,那为什么我们还要去解析呢?主要是为了让大家熟悉实例属性
紧接着就是 sentinel 哨兵模式的启动了,从最上面 main() 函数中我们看到,调用了 sentinelIsRunning()
函数进行启动处理的
事件
哨兵的操作是由时间事件进行操作的,也就是由 serverCron()
函数发起的
以 100毫秒的 频率执行 sentinelTimer()
事件,对 sentinel 各种事件进行周期性调度
在讲解 TILT 模式判断逻辑开始前,我们来回顾一下上面已经说到过的 TILT 模式,TILT 模式其实是一个 sentinel 特殊保护性的模式;哨兵的运行,其实非常依赖于系统时间,但是当系统时间被调整,或者哨兵中的流程因为某种原因(比如负载较高、IO发生阻塞、进程被信号停止等)而被阻塞时,哨兵的行为就会变得不可预知了。于是就有了 TILT 模式,进入 TILT 模式后,哨兵只定期发送命令用于收集信息,而不采取实质性的动作,比如不会进行故障转移流程;当有实例向 sentinel 发送 SENTINEL is-master-down-by-addr 的时候会被返回负值,因为当前情况的下线判断已经不准确了。当恢复正常30秒后,哨兵就是退出 TILT 模式
通过上面的时间事件我们可以看到,每次执行相隔 100毫秒,相当于每秒 10次,当然如果发生了异常(负载问题、IO 阻塞、进程信号停止等)或者 系统时钟发生了明显变化 等情况,sentinel 会上一周期执行的时间,与这次的时间进行对比,我们看一下具体逻辑
那么接下来我们在执行周期性函数sentinelHandleDictOfRedisInstances()
那么我们继续调试,查看周期性操作函数 sentinelHandleRedisInstance()
由于在载入配置的时候,主节点实例加入到字典表的时候默认是关闭的,所以一开始上面就调用了个 sentinel 和 实例的连接函数 sentinelReconnectInstance()
命令发送
接下来我们来解析 sentinelSendPeriodicCommands()
发送监控命令函数
接上面函数里面的逻辑,INFO 命令的回调函数为 sentinelInfoReplyCallback()
,进行一些简短的正确性判断和统计后,实际运算内容由 sentinelRefreshInstanceInfo()
函数 收集响应消息和处理角色变化,我们来看一下具体的逻辑
那么关于 PING 命令呢?上面我们提到了调用 sentinelSendPing()
函数,该函数的逻辑就比较简单了,发送 PING 命令,并设置回调函数 sentinelPingReplyCallback()
,再加以各项数据统计等,那我们接下来看看一下 回调函数的逻辑
还剩一个命令就是 PUBLISH 了,其实就是向 __sentinel__:hello
频道发送信息,处理函数为 sentinelSendHello()
,信息内容主要为 哨兵信息和主服务器节点信息,发送后 设置回调函数 sentinelPublishReplyCallback()
回调函数 sentinelPublishReplyCallback()
的逻辑就比较简单了,主要为更新统计信息
主/客 观下线状态判定
上面概况中我们提到 主观下线,如果 监控的服务器节点 在 down-after-milliseconds
设置的毫秒时效内没有响应检测,则会被判定为 主观下线;这个状态适用所有服务器节点;处理函数为 sentinelCheckSubjectivelyDown()
而 客观下线 为 主服务器节点在发生故障的时候,sentinel 会通过 is-master-down-by-addr
命令向其他 sentinel 节点询问 该主服务器节点的状态,如果超过 quorum
个数的哨兵节点都认为 主服务器节点不可达,则判定为 客观下线;这个状态只会针对 主服务器节点;操作函数为 sentinelCheckObjectivelyDown()
故障转移
确定客观下线后即将进行故障转移,处理函数为 sentinelFailoverStateMachine()
,我们来看一下详细逻辑
故障转移步骤是分步但是也是连续的,成功执行完一个状态,下一个时间事件顺序执行下一个状态,每一次时间事件执行一个状态,所以如果中途执行到某个状态发现主服务器节点可达了,那么故障转移结束,我们来看一下具体每个状态的逻辑
故障转移开始函数 sentinelFailoverWaitStart()
选择要晋升的从服务器节点函数 sentinelFailoverSelectSlave()
发送 slaveof no one 命令,从变主 处理函数为 sentinelFailoverSendSlaveOfNoOne()
等待选择的从服务器节点变成主服务器节点,如果超时就重新选择新的从服务器节点,处理函数为 sentinelFailoverWaitPromotion()
,不过逻辑比较简单,主要是判断是否超时
给所有从服务器发送 slaveof 通知,让他们同步 新的主服务器节点,处理函数为 sentinelFailoverReconfNextSlave()
我们上面提到了选举节点的逻辑还是有一些说法,接下来就进行详细逐一说明
上面我们说到给备选从节点数组排序,那么按照什么逻辑排序呢,我们来看一下 排序比较函数 compareSlavesForPromotion()
,总体的原则就是 优先级低的优先
、复制偏移量大的优先
、runid字典序小的优先
至此主要的逻辑我们分析了一遍,不过有点儿绕哈,由于很多函数都是站在不同角度(主服务器节点、从服务器节点、sentinel 节点)执行的,所以看的时候需要注意,另外就是发送命令基本都是异步的,所以可能存在响应不那么及时的情况,所以一般可能需要不止一次的循环运算
本文作者: wettper
本文链接: http://www.web-lovers.com/redis-source-sentinel.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!