Redis 的 Sentinel 系统原创
介绍 Redis 的 Sentinel 系统
技术是为了解决问题而生的,Redis 的 Sentinel 系统实现了 Redis 主从服务器的自动切换。
Sentinel 是 Redis 的高可用性解决方案:由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
Sentinel 系统监视服务器的原理
Sentinel 和一般 Redis 服务器的区别:Sentinel 本质上只是一个运行在特殊模式下的 Redis 服务器。
Sentinel 会读入用户指定的配置文件,为每个要被监视的主服务器创建相应的实例结构,并创建连向主服务器的命令连接和订阅连接:
- 命令连接用于,向主服务器发送命令请求;
- 订阅连接用于,接收指定频道的消息。
Sentinel 通过向主服务器发送 info 命令来获得主服务器属下所有从服务器的地址信息,并为这些从服务器创建相应的实例结构,以及连向这些从服务器的命令连接和订阅连接。
在一般情况下,Sentinel 以每十秒一次的频率向被监视的主服务器和从服务器发送 info 命令。当主服务器处于下线状态,或者 Sentinel 正在对主服务器进行故障转移操作时,Sentinel 向从服务器发送 info 命令的频率会改为每秒一次。
对于监视同一个主服务器和从服务器的多个 Sentinel 来说,它们会以每两秒一次的频率,通过向被监视服务器的 __ sentinel __:hello 频道发送消息来向其他 Sentinel 宣告自己的存在。
每个 Sentinel 也会从 __ sentinel __:hello 频道中接收其他 Sentinel 发来的信息,并根据这些信息为其他 Sentinel 创建相应的实例结构,以及命令连接。
Sentinel 只会与主服务器和从服务器创建命令连接和订阅连接, Sentinel 与 Sentinel 之间则只创建命令连接。
Sentinel 系统对主服务器执行故障转移的整个过程
在默认情况下,Sentinel 会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他 Sentinel 在内)发送 ping 命令,并根据实例对 ping 命令的回复来判断实例是否在线,当一个实例在指定的时长中连续向 Sentinel 发送无效回复时,Sentinel 会将这个实例判断为主观下线。
当 Sentinel 将一个主服务器判断为主观下线时,它会向同样监视这个主服务器的其他 Sentinel 进行询问,看它们是否同意这个主服务器已经进入主观下线状态。当 Sentinel 收集到足够多的主观下线投票之后,它会将主服务器判断为客观下线。
当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个 Sentinel 会进行协商,选举出一个领头 Sentinel,并由领头 Sentinel 对下线主服务器执行故障转移操作。
在选举产生出领头 Sentinel 之后,领头 Sentinel 将对已下线的主服务器执行故障转移操作,该操作包含以下三个步骤:
- 选出新的主服务器:在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器。
- 修改从服务器的复制目标:让已下线主服务器属下的所有从服务器改为复制新的主服务器。
- 将旧的主服务器变为从服务器:将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。
1、检测服务器的下线状态
在默认情况下,Sentinel 会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他 Sentinel 在内)发送 ping 命令,并根据实例对 ping 命令的回复来判断实例是否在线,当一个实例在指定的时长中连续向 Sentinel 发送无效回复时,Sentinel 会将这个实例判断为主观下线。
当 Sentinel 将一个主服务器判断为主观下线时,它会向同样监视这个主服务器的其他 Sentinel 进行询问,看它们是否同意这个主服务器已经进入主观下线状态。
当 Sentinel 收集到足够多的主观下线投票之后,它会将主服务器判断为客观下线,并发起一次针对主服务器的故障转移操作。
实例对 ping 命令的回复可以分为以下两种情况:
- 有效回复:实例返回 +pong、-loading、-masterdown 三种回复的其中一种。
- 无效回复:实例返回除 +pong、-loading、-masterdown 三种回复之外的其他回复,或者在指定时限内没有返回任何回复。
1、检测主观下线状态
down-after-milliseconds 选项(主观下线时长 选项)
Sentinel 配置文件中的 down-after-milliseconds 选项指定了 Sentinel 判断实例进入主观下线所需的时间长度。
如果一个实例在 down-after-milliseconds 毫秒内,连续向 Sentinel 返回无效回复,那么 Sentinel 就会将该实例标记为主观下线。修改这个实例所对应的实例结构,在结构的 flags 属性中打开 SRI_S_DOWN 标识,以此来表示这个实例已经进入主观下线状态。
down-after-milliseconds 选项的作用范围
用户设置的 down-after-milliseconds 选项的值,不仅会被 Sentinel 用来判断主服务器的主观下线状态,还会被用于判断主服务器属下的所有从服务器,以及所有同样监视这个主服务器的其他 Sentinel 的主观下线状态。
2、检测客观下线状态
当 Sentinel 将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这个主服务器的其他 Sentinel 进行询问,看它们是否也认为主服务器已经进入了下线状态(可以是主观下线或者客观下线)。
当 Sentinel 从其他 Sentinel 那里接收到足够数量的已下线判断之后,Sentinel 就会将主服务器判定为客观下线,并对主服务器执行故障转移操作。
客观下线状态的判断条件:当认为主服务器已经进入下线状态的 Sentinel 的数量,超过 Sentinel 配置中设置的 quorum 参数的值,那么该 Sentinel 就会认为主服务器已经进入客观下线状态。
检测客观下线的具体流程:
源 Sentinel 发送命令:Sentinel 使用 sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
命令询问其他 Sentinel 是否同意主服务器已下线。
目标 Sentinel 接收命令:当一个 Sentinel(目标 Sentinel)接收到另一个 Sentinel(源 Sentinel) 发来的该命令时,目标 Sentinel 会分析并取出命令请求中包含的各个参数,并根据其中的主服务器 IP 和端口号,检查主服务器是否已下线,然后向源 Sentinel 返回一条包含三个参数的 MultiBulk 回复作为该命令的回复。
- down_state:返回目标 Sentinel 对主服务器的检查结果,1 代表主服务器已下线,0 代表主服务器未下线
- leader_runid:可以是 * 符号或者目标 Sentinel 的局部领头 Sentinel 的运行 ID。*符号代表命令仅仅用于检测主服务器的下线状态,而局部领头 Sentinel 的运行 ID 则用于选举领头 Sentinel
- leader_epoch:目标 Sentinel 的局部领头 Sentinel 的配置纪元,用于选举领头 Sentinel。仅在 leader_ runid 的值不为 * 时有效,如果 leader_ runid 的值为 *,那么 leader_ epoch 总为 0
源 Sentinel 接收命令的回复:根据其他 Sentinel 对该命令的回复,Sentinel 将统计其他 Sentinel 同意主服务器已下线的数量,当这一数量达到配置指定的判断客观下线所需的数量时,Sentinel 就会将该实例标记为客观下线。修改这个实例所对应的实例结构,在结构的 flags 属性中打开 SRI_O_DOWN 标识,以此来表示这个实例已经进入客观下线状态。
Redis 检测客观下线状态 / 选举领头 Sentinel 的命令:sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
- 参数 ip:被 Sentinel 判断为主观下线的主服务器的 IP 地址
- 参数 port:被 Sentinel 判断为主观下线的主服务器的端口号
- 参数 current_epoch:Sentinel 当前的配置纪元,用于选举领头 Sentinel
- 参数 runid:该参数的值可以是 * 符号或者 Sentinel 的运行 ID。* 符号代表命令仅仅用于检测主服务器的客观下线状态,而 Sentinel 的运行 ID 则用于选举领头 Sentinel
2、选举领头 Sentinel
当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个 Sentinel 会进行协商,选举出一个领头 Sentinel,并由领头 Sentinel 对下线主服务器执行故障转移操作。
Sentinel 系统选举领头 Sentinel 的方法是对 Raft 算法的领头选举方法的实现。
选举领头 Sentinel 的规则
Redis 选举领头 Sentinel 的规则:过半数原则
每个发现主服务器进入客观下线的 Sentinel 都会要求其他 Sentinel 将自己设置为局部领头 Sentinel。在一个配置纪元里面,所有 Sentinel 都只有一次将某个 Sentinel 设置为局部领头 Sentinel 的机会,并且局部领头一旦设置,在这个配置纪元里面就不能再更改。
配置纪元实际上就是一个计数器,并没有什么特别的。
每次进行领头 Sentinel 选举之后,不论选举是否成功,所有 Sentinel 的配置纪元(configuration epoch)的值都会自增一次。
Sentinel 设置局部领头 Sentinel 的规则是先到先得:最先向目标 Sentinel 发送设置要求的源 Sentinel 将成为目标 Sentinel 的局部领头 Sentinel,而之后接收到的所有设置要求都会被目标 Sentinel 拒绝。
- 如果有某个 Sentinel 被半数以上的 Sentinel 设置成了局部领头 Sentinel,那么这个 Sentinel 成为领头 Sentinel。
- 如果在给定时限内,没有一个 Sentinel 被选举为领头 Sentinel,那么各个 Sentinel 将在一段时间之后再次进行选举,直到选出领头 Sentinel 为止。
因为领头 Sentinel 的产生需要半数以上 Sentinel 的支持,并且每个 Sentinel 在每个配置纪元里面只能设置一次局部领头 Sentinel,所以在一个配置纪元里面,只会出现一个领头 Sentinel。
选举领头 Sentinel 的命令
Redis 检测客观下线状态 / 选举领头 Sentinel 的命令:sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
- 参数 ip:被 Sentinel 判断为主观下线的主服务器的 IP 地址
- 参数 port:被 Sentinel 判断为主观下线的主服务器的端口号
- 参数 current_epoch:Sentinel 当前的配置纪元,用于选举领头 Sentinel
- 参数 runid:该参数的值可以是 * 符号或者 Sentinel 的运行 ID。* 符号代表命令仅仅用于检测主服务器的客观下线状态,而 Sentinel 的运行 ID 则用于选举领头 Sentinel
源 Sentinel 发送命令:当一个 Sentinel(源 Sentinel)向另一个 Sentinel(目标 Sentinel)发送该命令,并且命令中的 runid 参数不是 * 符号而是源 Sentinel 的运行 ID 时,这表示源 Sentinel 要求目标 Sentinel 将前者设置为后者的局部领头 Sentinel。
目标 Sentinel 接收命令:目标 Sentinel 在接收到sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
之后,将向源 Sentinel 返回一条命令回复,回复中的 leader_runid 参数和 leader_epoch 参数分别记录了目标 Sentinel 的局部领头 Sentinel 的运行 ID 和 配置纪元。
**源 Sentinel 接收命令的回复:**源 Sentinel 在接收到目标 Sentinel 返回的命令回复之后,会检查回复中 leader_epoch 参数的值和自己的配置纪元是否相同,如果相同的话,那么源 Sentinel 继续取出回复中的 leader_runid 参数,如果 leader_runid 参数的值和源 Sentinel 的运行 ID 一致,那么表示目标 Sentinel 将源 Sentinel 设置成了局部领头 Sentinel。
3、故障转移
在选举产生出领头 Sentinel 之后,领头 Sentinel 将对已下线的主服务器执行故障转移操作,该操作包含以下三个步骤:
- 选出新的主服务器:在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器。
- 修改从服务器的复制目标:让已下线主服务器属下的所有从服务器改为复制新的主服务器。
- 将旧的主服务器变为从服务器:将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。
1、选出新的主服务器
故障转移操作第一步要做的就是在已下线主服务器属下的所有从服务器中,挑选出一个状态良好、数据完整的从服务器,然后向这个从服务器发送 slaveof no one 命令,将这个从服务器转换为主服务器。
新的主服务器是怎样挑选出来的
领头 Sentinel 会将已下线主服务器的所有从服务器保存到一个列表里面,然后按照以下规则,一项一项地对列表进行过滤:
- 根据 在线状态 过滤:删除列表中所有处于下线或者断线状态的从服务器,这可以保证列表中剩余的从服务器都是正常在线的。
- 根据 通信情况 过滤:删除列表中所有最近五秒内没有回复过领头 Sentinel 的 info 命令的从服务器,这可以保证列表中剩余的从服务器都是最近成功进行过通信的。
- 根据 断连时间 过滤:删除所有与已下线主服务器连接断开超过 down-after- milliseconds * 10 毫秒的从服务器:down-after-milliseconds 选项指定了判断主服务器下线所需的时间,而删除断开时长超过 down-after- milliseconds * 10 毫秒的从服务器,则可以保证列表中剩余的从服务器都没有过早地与主服务器断开连接,换句话说,列表中剩余的从服务器保存的数据都是比较新的。
- 根据 从库的优先级 筛选:之后,领头 Sentinel 将根据从服务器的优先级,对列表中剩余的从服务器进行排序,并选出其中优先级最高的从服务器。
- 根据 复制偏移量 筛选:如果有多个具有相同最高优先级的从服务器,那么领头 Sentinel 将按照从服务器的复制偏移量,对具有相同最高优先级的所有从服务器进行排序,并选出其中偏移量最大的从服务器(复制偏移量最大的从服务器就是保存着最新数据的从服务器)。
- 根据 运行 ID 筛选:最后,如果有多个优先级最高、复制偏移量最大的从服务器,那么领头 Sentinel 将按照运行 ID 对这些从服务器进行排序,并选出其中运行 ID 最小的从服务器。
2、修改从服务器的复制目标
当新的主服务器出现之后,领头 Sentinel 下一步要做的就是,让已下线主服务器属下的所有从服务器去复制新的主服务器,这一动作可以通过向从服务器发送 slaveof 命令来实现。
3、将旧的主服务器变为从服务器
故障转移操作最后要做的是,将已下线的主服务器设置为新的主服务器的从服务器。
因为旧的主服务器已经下线,所以这种设置是保存在服务器对应的实例结构里面的,当服务器重新上线时,Sentinel 就会向它发送 slaveof 命令,让它成为新主服务器的从服务器。
总结 Sentinel 系统中的周期命令
在 Sentinel 系统中,有很多地方都会以一定的频率向指定的服务器发送命令,下面对所有周期命令进行总结。
检测下线状态的 ping 命令:在默认情况下,Sentinel 会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他 Sentinel 在内)发送 ping 命令,并根据实例对 ping 命令的回复来判断实例是否在线。
获取服务器信息的 info 命令:在一般情况下,Sentinel 以每十秒一次的频率向被监视的主服务器和从服务器发送 info 命令,当主服务器处于下线状态,或者 Sentinel 正在对主服务器进行故障转移操作时,Sentinel 向从服务器发送 info 命令的频率会改为每秒一次。
参考资料
《Redis 设计与实现》书籍