什么是高可用?
高可用 这个词一般出现于分布式系统,指通过设计减少系统不能提供服务的时间,通俗来说,系统什么时候都能用,特别NB,就是高可用。
- 假设系统一直能够提供服务,我们说系统的可用性是100%。
- 如果系统每运行100个时间单位,会有1个时间单位无法提供服务,我们说系统的可用性是99%。
- 很多公司的高可用目标是4个9,也就是99.99%,这就意味着,系统的年停机时间为8.76个小时。
Redis为何高可用?
当“高可用”这个词结合Redis,我首先会想到以下点:
- redis的多机部署
- 主从服务器之类的
- 哨兵、故障转移
- 数据丢失之后的数据同步,RDB、AOF之类的
接下来我会对这些点进行详细分析。
原因一、哨兵机制
哨兵是 redis 集群机构中非常重要的一个组件,哨兵集群会对redis集群实时三项监控,来监控节点的可用性,这是高可用的重要保障之一。
1、当一个Sentinel启动时, 它需要执行以下步骤:
- 初始化服务器。
- 将普通Redis服务器使用的代码替换成Sentinel专用代码。
- 初始化Sentinel状态。
- 根据给定的配置文件, 初始化Sentinel的监视主服务器列表。
- 创建连向主服务器的网络连接
主要有以下功能:
- 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
- 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行。
- 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
- 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
2、哨兵对节点的三项监控
(1)info命令:
每隔 10 秒,每个哨兵节点都会向主、从 Redis 数据节点发送 info 命令,获取新的拓扑结构信息。
Redis 拓扑结构信息包括了:
- 本节点角色:主或从
- 主从节点的地址
- 端口信息。
这样,哨兵节点就能从 info 命令中自动获取到从节点信息,因此那些后续才加入的从节点信息不需要显式配置就能自动感知。
(2)向 sentinel:hello 频道同步信息
每隔 2 秒,每个哨兵节点将会向 Redis 数据节点的 sentinel:hello 频道同步自身得到的主节点信息以及当前哨兵节点的信息。
由于其他哨兵节点也订阅了这个频道,因此实际上这个操作可以交换哨兵节点之间关于主节点以及哨兵节点的信息。
这一操作实际上完成了两件事情:
发现新的哨兵节点:如果有新的哨兵节点加入,此时保存下来这个新哨兵节点的信息,后续与该哨兵节点建立连接。 交换主节点的状态信息,作为后续客观判断主节点下线的依据。
(3)向数据节点做心跳探测
每隔 1 秒,每个哨兵节点向主、从数据节点以及其他 Sentinel 节点发送 Ping 命令做心跳探测,这个心跳探测是后续主观判断数据节点下线的依据。
主观下线
上面三个监控任务中的第三个探测心跳任务,如果在配置的 down-after-milliseconds 之后没有收到有效回复,那么就认为该数据节点“主观下线(sdown)”。
为什么称为“主观下线”?因为在一个分布式系统中,有多个机器在一起联动工作,网络可能出现各种状况,仅凭一个节点的判断还不足以认为一个数据节点下线了,这就需要后面的“客观下线”。
客观下线
当一个哨兵节点认为主节点主观下线时,该哨兵节点需要通过”sentinel is-master-down-by addr”命令向其他哨兵节点咨询该主节点是否下线了,如果有超过半数的哨兵节点都回答了下线,此时认为主节点“客观下线”。
3、选举领头Sentinel
结合上一小结,当一个主服务器被判断为客观下线时, 监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器执行故障转移操作。
4、故障转移
在选举产生出领头Sentinel之后, 领头Sentinel将对已下线的主服务器执行故障转移操作,包含以下三个步骤:
- 在已下线主服务器属下的所有从服务器里面, 挑选出一个从服务器, 并将其转换为主服务器。
- 让已下线主服务器属下的所有从服务器改为复制新的主服务器。
- 将已下线主服务器设置为新的主服务器的从服务器, 当这个旧的主服务器重新上线时, 它就会成为新的主服务器的从服务器。
(1)新的主服务器选举规则
领头Sentinel会将已下线主服务器的所有从服务器保存到一个列表里面, 然后按照以下规则, 一项一项对列表进行过滤:
- 删除列表中所有处于下线或者断线状态的从服务器, 这可以保证列表中剩余的从服务器都是正常在线的。
- 删除列表中所有最近五秒內没有回复过领头Sentinel的ZNFO命令的从服务器,这可以保证列表中剩余的从服务器都是最近成功进行过通信的。
- 删除所有与已下线主服务器连接断开超过一定时间的服务器,可以保证列表中剩余的从服务器都没有过早地与主服务器断开连接 换句话说,列表中剩余的从服务器保存的数据都是比较新的。
- 之后, 领头Sentinel将根据从服务器的优先级, 对列表中剩余的从服务器进行排序,并选出其中优先级最高的从服务器。
- 如果有多个具有相同最高优先级的从服务器, 那么领头Sentinel将按照从服务器的复制偏移量, 对具有相同最高优先级的所有从服务器进行排序, 并选出其中偏移量最大的从服务器(复制偏移量最大的从服务器就是保存着最新数据的从服务器) 。
- 最后, 如果有多个优先级最高、 复制偏移量最大的从服务器, 那么领头Sentinel将按照运行ID对这些从服务器进行排序, 并选出其中运行ID最小的从服务器。
(2)选举之后,让其他从服务器服从主服务器
当新的主服务器出现之后, 领头Sentinel下一步要做的就是, 让已下线主服务器属下的所有从服务器去复制新的主服务器, 这一动作可以通过向从服务器发送SLAVEOF来实现。
(3)将旧的主服务器变为从服务器
故障转移操作最后要做的是, 将已下线的主服务器设置为新的主服务器的从服务器。
5、哨兵的核心知识
- 哨兵至少需要 3 个实例,来保证自己的健壮性。
- 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
- 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
- 由一个或多个Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,及属下的所有从服务器。
- 并在被监视的主服务器进入下线状态时, 自动将下线主服务器属下的某个从服务器升级为新的主服务器, 然后由新的主服务器代替已下线的主服务器继续处理命令请求。
- 另外,其实一个哨兵节点,就是一个redis服务器。只不过没有数据库功能,且命令集被更换为专用的哨兵命令集而已。
原因二、数据库的主从复制
不同数据节点之间的数据同步和恢复也构成了高可用的一环,Redis 中主从节点复制数据有全量复制和部分复制之分。
全量复制:
- 将库中所有的键通过RDB文件的形式,发送给从服务器进行备份。
接下来将相许讲解部分复制:
1、部分复制
(1)偏移量
主从服务器各自维护一个同步偏移量,完成一次读写,便按照字节数增加偏移量,如果某一时刻从服务器崩溃了,那么它自然将会失去与主服务器的同步。等到再次连接成功时,只需要从服务器主动发送自己的偏移量,在主服务器中与主服务器的偏移量进行比对,主服务器就会将没有进行同步的数据单独发过去,节省了大量的开销。
但是如果主服务器、从服务器当前的偏移量为10086,突然主服务器多写了100的偏移量,但是在向从服务器传播偏移量的时候断电了,从服务器——A断线了,信号没传过去。那么即使A立即连上,这个偏移量为100的数据,应该怎么恢复呢?这就要引出一个概念——复制积压缓冲区。
(2)复制积压缓冲区
复制积压缓冲区存在于主服务器中,缓冲区内会保存着一部分最近传播的写命令, 并且缓冲区会为每个字节记录相应的复制偏移量。当从服务器重新连上主服务器时, 从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器, 主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作:
- 如果offset偏移量之后的数据(也即是偏移量offset+1开始的数据)仍然存在于复制积压缓冲区里面, 那么主服务器将对从服务器执行部分重同步操作。
- 相反, 如果 offset偏移量之后的数据已经不存在于复制积压缓冲区, 那么主服务器将对从服务器执行完整重同步操作。
还是刚刚那个例子:
- 从服务器A断线之后立即重连,并向主服务器发送PSYNC命令, 报告自己的复制偏移量为10086(这个时候主服务器的偏移量应该为10186)。
- 主服务器将检査他自己这儿偏移量10086之后的数据是否存在于复制积压缓冲区里面, 如果是,则主服务器向从服务器发送CONTINUE回复, 表示数据同步将以部分重同步模式来进行。接着主服务器会将复制积压缓冲区**10086偏移量之后的所有数据(偏移量为10087至10186)都发送给从服务器。
- 从服务器只要接收这33字节的缺失数据, 就可以回到与主服务器一致的状态。
除了复制偏移量和复制积压缓冲区之外, 实现部分重同步还需要用到服务器运行ID
(3)服务器运行ID
- 每个Redis服务器, 不论主服务器还是从服务, 都会有自己的运行ID。
- 运行ID在服务器启动时自动生成, 由40个随机的十六进制字符组成, 例如53b9b28df8042fdc9ab5e3fcbbbabffId5dce2b3o
- 当从服务器对主服务器进行初次复制时, 主服务器会将自己的ID传送给从服务器,而从服务器则会将这个运行ID保存起来。(让小弟记住自己的老大是谁)
- 当从服务器断线并重新连上一个主服务器时, 从服务器将向当前连接的主服务器发送之前保存的运行ID。(小弟与老大的相认)
- 如果从服务器保存的运行ID和当前连接的主服务器的运行ID相同, 那么说明从服务器断线之前复制的就是当前连接的这个主服务器, 主服务器可以继续尝试执行部分重同步操作。(认亲成功,可以进行同步,使用什么挤压缓冲区什么的)
- 相反地, 如果从服务器保存的运行ID和当前连接的主服务器的运行ID并不相同,那么说明从服务器断线之前复制的主服务器并不是当前连接的这个主服务器, 主服务器将对从服务器执行完整重同步操作。(认亲失败,直接RDB)
- 举个例子, 假设从服务器原本正在复制一个运行ID为53b9b28df8042fdc9ab5e3fcbbbabffld5dce2b3的主服务器, 那么在网络断开, 从服务器重新连接上主服务器之后,从服务器将向主服务器发送这个运行ID,主服务器根据自己的运行ID是否53b9b28df8042fdc9ab5e3fcbbbabffId5dce2b3来判断是执行部分重同步还是执行完整重同步。
|