Redis - Redis主从数据一致性和哨兵机制

Redis - Redis主从数据一致和哨兵机制

  • 前言
  • 一. Redis 主从库实现
    • 1.1 主从库之间的同步
    • 1.2 主从级联模式
    • 1.3 主从库间的网络连接中断问题
    • 1.4 总结
  • 二. 哨兵机制
    • 2.1 哨兵机制基本流程
    • 2.2 新主库的选举机制
    • 2.3 哨兵机制 - 发布订阅机制
    • 2.4 哨兵领导的选举
    • 2.5 小总结

前言

在这之前,主要复习了RedisAOF日志还有RDB快照。它是高可用的一个重要保障,就是数据尽量少丢失。而高可用还有一点就是服务尽量少中断,那么这里就需要通过增加副本的冗余了。也就是Redis的主从架构了。

一. Redis 主从库实现

Redis提供了主从库模式,采用读写分离模式

  • 读操作:主从库都可以进行读操作。
  • 写操作:只有主库可以执行。随后主库将结果同步给从库。

Redis - Redis主从数据一致性和哨兵机制_第1张图片

1.1 主从库之间的同步

Redis启动多个实例之后,开启主从库关系也是比较简单的。实例之间可以通过 replicaof 命令来链接。例如我们在实例B上执行以下命令:

replicaof 实例A 6379

那么此时实例B就成为了实例A的从库。并会从实例A上复制数据。两个实例之间第一次的数据同步有三个阶段,如图:
Redis - Redis主从数据一致性和哨兵机制_第2张图片
阶段一:建立连接,协商同步。

这一个阶段,也就是主从库之间建立起连接,并为全量复制做准备。我们可以看到,从库主要是向主库发送了一条 psync 命令。该命令的目的:表示要进行数据同步。 该命令包含两个参数:

  • runID:实例的唯一ID,启动的时候会自动随机生成。由于主从库之间此时是第一次连接,因此从库无法得知主库的runID,因此这里是一个问号。
  • offset:偏移量,可以看做数据复制的进度。 -1 代表是第一次复制。

同样地,从库发送了这样的请求,主库也要发回去,类似于TCP的三次握手。主库接收到 psync 命令后,通过 FULLRESYNC 响应命令同样带上这两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。

FULLRESYNC:表示采用全量复制,将主库当前的所有数据都复制给从库。那么问题来了,主从库之间是如何进行数据的传输的?那就来到了第二阶段了。


阶段二:主库同步数据给从库。

这个过程依赖于RDB文件。大概流程如下:

  1. 主库执行了bgsave命令,生成了RDB文件,然后发送给从库。
  2. 从库接收到RDB文件后,先清空当前数据库,然后加载RDB文件。

其中,有这么几个注意点:

  • 主从库数据同步的过程中,主库并不会阻塞,依旧可以正常的处理写请求。
  • 同步阶段,主库的写操作并不会记录在RDB文件中。而是保存在主库内存中的 replication buffer

准确来说, replication buffer中保存了以下三种情形下的数据更新操作:

  • 主库执行bgsave命令,产生快照的过程中。
  • 主库发送RDB文件到从库,网络传输的过程中。
  • 从库将RDB文件恢复到本地内存,即数据恢复的过程中

阶段三:主库将第二阶段中新的数据更新操作发送给从库。

此时从库已经将RDB文件恢复到自己的内存,数据已经基本完成同步了。但是第二阶段中,主库的写操作功能依旧能够使用,而主库将这类数据更新操作都记录到了 replication buffer。因此在最后一个阶段,从库还需要执行这些操作,从而实现主从库之间数据的完全同步。

1.2 主从级联模式

从上面我们可以得知,主库之间如何建立主从关系,以及第一次建立关系的过程。我们也可以想到,这个过程中最耗时的两个操作:

  • RDB文件的生成
  • RDB文件的传输

我们再来看下这样的主 - 从架构图:
Redis - Redis主从数据一致性和哨兵机制_第3张图片

但是这种简单单一的主- 从设计模式有着一定的缺陷:

  1. 主库每个一个从库建立连接,就要传输一个RDB文件。但是从库万一有很多个呢?
  2. RDB文件是主库通过fork一个子进程然后写入的。fork的过程中,主库是阻塞的。那从库很多的情况下,就容易发生主库阻塞的情况。

为了解决上述问题,主要可以通过减缓主库的压力。那么只要将某一个从库变成其他从库的主库就好了。也就是二级主库(我自己命名的)。看图会更直观点,主从从架构
Redis - Redis主从数据一致性和哨兵机制_第4张图片

1.3 主从库间的网络连接中断问题

背景:

虽然主从架构一旦完成,并且实例之间都已经建立起了长连接,那么读写分离的功能也就有了,而且高可用。但是这类架构有个问题是无法避免的:如果实例之间的网络连接断了怎么办?

  • Redis 2.8 之前,如果主从库在命令传播时出现了网络闪断,则会重新进行一次全量复制。 开销太大。
  • Redis 2.8 之后,如果主从库在命令传播时出现了网络闪断,则会重新进行一次增量复制。 期间主要通过 repl_backlog_buffer 缓冲区,实现增量数据的同步的。

接下来说下增量同步的原理:

首先主从库断开连接之后,主库会将期间收到的写操作命令写入缓冲区:

  • repl_backlog_buffer。一种环形缓冲区,用于为增量同步做保障主库记录自己写到的数据偏移量。从库记录自己读到的数据偏移量。

对于主库而言,不断接收新的写操作,那么在repl_backlog_buffer缓冲区中的偏移量就会越来越大,叫做master_repl_offset。对应的,从库也在不断地复制写命令,在缓冲区的读位置也在不断地增大,对应的叫做master_repl_offset。正常情况下,两者基本保持相同。示意图大致如下:
Redis - Redis主从数据一致性和哨兵机制_第5张图片


在了解完repl_backlog_buffer这个缓冲区之后,回到正轨,那么在主从连接恢复的那一刻起,主从之间的数据是如何保证同步的呢?这个阶段和主从之间建立第一次连接比较相似:

  1. 主从库的连接恢复之后,从库首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库,主库计算 master_repl_offsetslave_repl_offset 之间的差值。一般主位移 > 从位移。(因为主库还能够有写操作)
  2. 将从库缺少的位移部分所对应的命令发送给从库,从库执行完毕,就完成了数据同步。
    Redis - Redis主从数据一致性和哨兵机制_第6张图片
    除此之外,以下还要注意几点:
  • repl_backlog_buffer这个值最好设置尽量大一点,否则,倘若主从库断开时间太久,主库上写操作太多,将这个区域写满,那么从库就只能进行一次全量同步操作。因为没有位置可以存放从库读的位置。因此配置大一点可以降低主从断开后开启全量同步的概率。
  • 由于repl_backlog_buffer是一个环形缓冲区,因此在缓冲区写满后,主库会继续写入此时就会覆盖掉之前写入的操作如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。
  • repl_backlog_buffer存在于主节点,其他的从库的offset偏移量都是相对于该缓冲区而言的。
  • replication buffer存在于各个从节点。用于主节点与各个从节点间数据的批量交互。无论全量还是增量同步,数据都通过其来进行交互。

1.4 总结

到目前为止主要说了Redis的主从库同步的基本原理:

  • 主从库之间第一次建立连接:全量复制。因此建议一个Redis的实例不要太大,否则在RDB文件传输和创建的过程会比较耗时。同时避免只有一个主库和多个从库建立主从关系,可以建立主从从模式来缓解主库的压力。
  • 主从库基于长连接进行命令的传播。
  • 若主从库之间连接发生断开,Redis主要通过增量复制来保证数据的一致性。重点在于repl_backlog_buffer这个圆形缓冲区。记录了主从库的写/读偏移量,通过两者的差值可以计算出断开连接期间从库缺失的动作。

题外话:为何主从全量同步使用的是RDB文件而不是AOF日志呢?

  1. RDB文件的内容是经过压缩的二进制数据。而AOF文件记录的则是每一次操作命令,不仅冗余而且容易变得很大。因此全量数据同步的时候,传输RDB文件可以降低对网络贷款的消耗。同时从库在加载RDB文件的时候,读取和解析速度都要快于AOF。(文件小 + 二进制内容)
  2. 倘若使用AOF日志作为全量同步,意味着必须打开AOF功能,随之而来的也必须设置AOF的文件刷盘策略。倘若选择不当,就会严重影响Redis性能。相反,RDB快照只需要主从同步数据的时候以及定时备份的时候才会触发生成。一般都业务场景足以应付,因此没有开启AOF的必要。

二. 哨兵机制

主从架构中,如果从库挂掉了,实质上的影响不是很大。因为写操作,是由主库来提供的。而读操作,主从库都有提供。但是如果主库挂掉了,该怎么办呢?数据往哪写是个问题。因此这种时候需要新选出一个主库,将某个从库切换为主库。这个过程就使用到了哨兵机制。

2.1 哨兵机制基本流程

哨兵主要负责三个任务:

  1. 监控: 周期性的给所有主从库发送PING命令,检查它们是否正常运行。倘若主从库没有在规定的时间内响应PING命令,那么将会被认定为下线状态。若是主库被认定为下线,那么就开始切换主库。
  2. 新主库的选举: 从剩余的几个从库中,按照一定的规则选择一个实例作为新的主库。
  3. 信息通知: 哨兵将新主库的连接信息发送给其他从库,然后执行replicaof命令,和新主库建立连接,并进行数据复制。同时哨兵还负责将新主库的连接信息告知客户端,让新的读写请求发送到新主库上。
    Redis - Redis主从数据一致性和哨兵机制_第7张图片

由于在实际生产过程中,难免遇到网络波动,或者是主库在某一时间内的压力比较大,因此无法及时的响应哨兵发送的PING命令,从而被认定为下线状态,开启了新主库的选举。但实际上,主库并没有挂掉。因此为了防止这样的误判发生,哨兵机制通常采用了多实例组成的集群模式进行部署,即哨兵集群。

不仅如此,由于主库的地位比较高,对于其下线状态的判断也是比较特殊的只有大多数的哨兵实例都判断主库已经主观下线了,那么主库才会被标记为客观下线。这时才会真正触发新主库的选举以及后续流程。

  1. 主观下线:某一个哨兵认为某个实例已经下线了。一对一。down-after-milliseconds设置的规定时间内若没有响应PING,则认为主观下线。 配置时间越短,代表哨兵越敏感。
  2. 客观下线:大多数哨兵认定某个主库主观下线,那么此时该主库属于客观下线。即少数服从多数,多对一
    Redis - Redis主从数据一致性和哨兵机制_第8张图片

2.2 新主库的选举机制

哨兵负责任务中,尤其重要的就是第二点,关于新主库的选举,上文提到过,其会以一定的规则来选举出一个新的实例。主要流程分为两大步:

  1. 过滤不满足条件的。
  2. 打分排序,选分数最高的。
    Redis - Redis主从数据一致性和哨兵机制_第9张图片

首先第一点,哪些从库是不满足条件的:

  1. 网络连接状态不好:即某个从库和主库之间总是发生断连操作,并且断连次数超过阈值。
  2. 从库处于下线状态:PING命令没有及时回应。

其次是打分环节:

  1. 第一轮:优先级打分:通过设置slave-priority给从库设置不同的优先级,数字越小代表优先级越大。
  2. 第二轮:同步度打分:在第一章节我们提到过主从之间在repl_backlog_buffer环形缓冲区中分别有着master_repl_offsetslave_repl_offset那么两者差值最小的,也就是从库同步程度最接近主库的,其分数也会最高。
  3. 第三轮:ID号打分:每个实例都会有一个随机生成的唯一ID在优先级和复制进度都相同的情况下,ID 号最小的从库得分最高,会被选为新主库。

提问:哨兵在操作主从切换的过程中,客户端能否正常地进行请求操作?

回答:若客户端使用了读写分离的前提下,读操作是可以在从库上正常执行的。但此时对于写操作。因为主库已经挂了,并且还没有选举出新的主库,因此这期间的写请求就会失败持续时间 = 哨兵切换主从的时间 + 客户端感知到新主库的时间


上文主要提到了监控阶段的几个要点:主观下线、客观下线等。以及选举新主库的一个大致流程。那么最后的通知部分还是没有讲的。当哨兵完成主从切换后,客户端需要及时感知到主库发生了变更,然后把缓存的写请求写入到新库中,保证后续写请求不会再受到影响

提问:哨兵在完成主从切换后,还做了什么事情?

回答:

  1. 哨兵将一个从库选举为新主库后,就会将其的地址写入到自己实例的pubsub中。 (下文详细介绍)
  2. 客户端一般订阅这个pubsub,当其有数据的时候,客户端就能感知到主库发生了变更,同时拿到了最新的主库地址。那么后续的写请求往该地址发送即可。

注意:因为主库可能会挂,然后通过哨兵机制选举出一个新的主库,因此再客户端访问主从库的时候,不能将地址写死,一般从哨兵集群中获取最新的地址。(SDK也有提供)


提问:哨兵集群中有实例挂了,会影响主库状态判断和选主吗?

回答:只要集群中大多数节点状态正常,集群依旧可以对外提供服务。


提问:哨兵集群多数实例达成共识,判断出主库“客观下线”后,由哪个实例来执行主从切换?

回答:由哨兵领导者来完成主从切换。哨兵领导者这部分用了Raft算法。选举过程大致如下(下文详细介绍):

  1. 每个哨兵都有一个超时时间,超时后每个哨兵会请求其他哨兵为自己投票。
  2. 其他哨兵节点对收到的第一个请求进行投票确认
  3. 一轮投票下来后,首先达到多数选票的哨兵节点成为领导者。
  4. 如果没有达到多数选票的哨兵节点,那么会重新选举。

2.3 哨兵机制 - 发布订阅机制

哨兵的配置如下:

sentinel monitor <主库名称> <主库ip> <端口号> <quorum(下文会说)>

每个哨兵配置的时候,只有主库相关的信息,而哨兵和哨兵之间却没有配置,那么哨兵实例彼此不知道对方的地址,那么如何组成集群的呢?主要通过pubsub机制,即Published以及Subscribe。发布订阅机制。


哨兵集群组成的原理:

  1. 哨兵一旦和主库建立起连接,就可以在主库上发布消息(自己的IP和端口等),同样可以从主库上订阅消息,获得其他哨兵发布的信息。 也因此哨兵和哨兵之间能够知道彼此的端口和IP地址。
  2. 哨兵或者其他的主从库,只有订阅了同一个频道的应用,才能通过发布的消息进行信息的交换。这个频道的概念,相当于KafkaTopic主题的概念。
    Redis - Redis主从数据一致性和哨兵机制_第10张图片

哨兵除了要和各个哨兵实例之间建立连接,搭建哨兵集群以外。还要和从库建立连接。不然,当主库挂掉的时候,哨兵又怎能去从从库当中选举新主库呢?那么哨兵又是如何知道从库的IP地址和端口的呢?


哨兵和从库之间建立连接的原理:

  1. 哨兵先向主库发送INFO命令。主库接收到后,将从库列表数据返回给哨兵。
  2. 哨兵根据从库列表中的信息,和每个从库建立连接。并持续对从库进行监控。
  3. 然后由于哨兵和哨兵之间也存在连接,因此就完成了哨兵集群的每个实例都和所有的从库建立连接。
    Redis - Redis主从数据一致性和哨兵机制_第11张图片

接下来就需要了解哨兵和客户端之间的通知问题了,上文提到了订阅的消息频道,在同一个频道下的不同实例(哨兵。主从库)之间是可以互相通知的。这里面有几个Redis中重要的事件和对应的频道:
Redis - Redis主从数据一致性和哨兵机制_第12张图片

这样客户端就可以从哨兵这里订阅到各种各样的消息,流程如下:

  1. 客户端读取哨兵的配置文件。从而拿到哨兵的地址和端口。
  2. 和哨兵建立网络连接,执行订阅命令,获取事件消息。
# 所有实例进入客观下线状态的事件
SUBSCRIBE +odown
# 订阅所有的事件
PSUBSCRIBE  *

2.4 哨兵领导的选举

上文我们提到过,当主库挂掉了,由哨兵集群来选举出一个新的主库,而主从库切换过程则交给一个特殊的哨兵 — 领导者哨兵 来执行。同时哨兵集群倘若要判定主库客观下线,需要有一定数量的实例都认为该主库已经主观下线。这时候才能开启主从的选举和切换。大概的流程如下:

  1. 当任何一个哨兵判断主库主观下线后,就会给其他哨兵发送 is-master-down-by-addr 命令。

  2. 其他哨兵接收到后,会根据自己和主库的连接情况,做出 Y(赞成) 或者 N(反对) 的响应。

  3. 若某个哨兵A,获得了一定数量的赞成票数,并且超过了仲裁所需的阈值,即quorum配置(配置哨兵的时候,最后一个参数),那么此时就可以标记主库为客观下线。

  4. 此时,该哨兵A就会发送命令给其他哨兵,表明希望由自己来执行主从切换,并让所有其他哨兵进行投票。即领导者哨兵的选举过程。

备注:将主库标记为客观下线的哨兵可以继续发起让自己来执行主从切换的投票。


成为领导者哨兵需要满足两个条件:

  1. 拿到半数以上的赞成票。
  2. 拿到的总赞成票数要 >= 哨兵配置文件中的majority值。

哨兵领导选举过程如图:
Redis - Redis主从数据一致性和哨兵机制_第13张图片

注意:

  1. 每个哨兵最多投一票赞成票和一票反对票。
  2. 要先判定客观下线,才能开启leader选举,才能给自己投票。而其他的哨兵实例会把票投给第一个来要票的请求,其后的都拒绝。
  3. 判断主观下线时,哨兵之间的响应Y/N和领导者选举的Y/N含义是不同的,希望大家做出区分。前者是用来判断主库是否和某个哨兵连接正常。后者是用来选举领导者哨兵的一个投票机制。 切记,这是两个不一样的事情。
  4. 最好保证所有哨兵实例的配置是一致的,尤其是主观下线的判断值 down-after-milliseconds
  5. 只有进入到选举流程的哨兵,才能自己给自己先投一票,如果它还没有进入选举流程,只能给别人投票。

最后一点做个区分:

  • majority:允许哨兵进行主从库切换的最少哨兵数量。先拿到这个数量的哨兵就是领导者哨兵。
  • quorum:确认主库客观下线的最少哨兵数量。

提问:Redis 1主4从,5个哨兵,哨兵配置quorum为2,如果3个哨兵故障,当主库宕机时,哨兵能否判断主库“客观下线”?能否自动切换?

回答:

  1. 由于哨兵还有2个存活,因此主库宕机的时候,2个哨兵都能感知到主库的宕机,即主观下线,并且互相发送请求进行确认。最终2台数量达到了quorum的值。因此哨兵集群此时可以判定主库是客观下线的。
  2. 哨兵集群不能够完成主从切换。原因在于选举领导者哨兵的时候,哨兵必须拿到超过半数的选票,哨兵集群共有5台机器,半数为 5/2 +1 = 3,而存活的哨兵只有2个,因此无论怎么样也达不到这样的票数要求。因此无法完成主从切换。

2.5 小总结

到这里讲的内容主要有这么几点:

  1. 哨兵集群的构建主要通过pub/sub机制来组成。
  2. 哨兵集群基于INFO命令和所有从库建立连接。
  3. 哨兵和从库之间可以通过pub/sub机制完成消息的订阅和事件通知。
  4. 哨兵认定主库客观下线,需要有超过quorum值数量的哨兵认定主库为主观下线。认定了主库为客观下线的哨兵有资格开始成为领导者哨兵的候选人,并发起投票。
  5. 只有候选人才能够投自己一票,其他的哨兵(未认定主库为客观下线的)只能够投给第一个接收到的投票请求。并且只能投赞成票一次。后者都是反对票。

你可能感兴趣的:(Redis,redis,数据库,缓存)