Redis核心技术与实战【学习笔记】 - 3.Redis服务高可靠

1.数据同步:主从库如何实现数据一致?

前面我们学习了 AOF 和 RDB,如果 Redis 发生了宕机,它们可以分别通过 AOF 日志和重新读入 RDB 文件的方式恢复数据,从而保证尽量较少丢失数据,提升可靠性。

不过,即使使用了这两种方法,也依然存在服务不可用的问题。比如说,只运行一个 Redis 实例,如果这个实例宕机了,它在恢复期间,是无法服务新来的数据操作请求的。

我们常说的 Redis 具有高可靠其实有两层含义,一是数据尽量减少丢失,而是服务尽量减少中断。AOF 和 RDB 保证了前者,而对于后者,Redis 的做法是增加副本冗余量。将一份数据同时保存在多个实例上。这样,即使有一个实例出现了故障,需要一段时间才能恢复,其他实例也可以对外提供服务,不影响业务使用。

Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。

  • 读操作:主库、从库都可以接收
  • 写操作:首先到主库执行,然后,主库将写操作同步给从库。

为什么采用读写分离的方式?
设想一下,如果不管是主库还是从库,都能接收客户端的写操作,那么,一个直接的问题就是:如果客户端对同一个数据(例如 k1)前后修改了三次,每一次修改请求都发送到不同的实例上执行,那么,这个数据在这三个实例上的副本就不一致了。在读取这个数据的时候,就可能读到旧值。
如果我们非要保持这个数据在三个实例上一致,就涉及加锁、实例间协商是否完成修改等一些列操作,但这会带来巨额的开销,当然是不能接受的。
而主从模式一旦采用了读写分离,所有数据的修改只会在主库上进行,不用协调三个实例。主库有了最新的数据后,会同步给从库,这样,主从库的数据就是一致的。

主从库同步是如何完成的?主库是一次性传给从库,还是分批同步?要是主从库网络断连了,数据还能保持一致吗?

我们先来看下主从库间的第一次同步是如何进行的,这也是 Redis 实例建立主从库模式后的规定动作。

1.1 主从库间如何进行第一次同步?

当我们启动多个 Redis 实例的时候,它们之间就可以通过 replicaof (Redis 5.0 之前使用 slaveof)命令形成主从库的关系,之后会按照三个阶段完成数据的第一次同步。

例如,现在有实例 1(ip:172.16.19.3)和实例 2(ip:172.16.19.5),我们在实例2上执行以下这个命令后,实例 2 就变成了实例 1 的从库,并从实例 1 上复制数据:

replicaof 172.16.19.3 6379

接下来,主从数据库间数据的第一次同步的三个阶段了。先看下图
在这里插入图片描述

第一阶段

第一阶段是主从库建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了

具体来说,从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID复制进度两个参数。

  • runID 是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为 “?”。
  • offset,此时设为 -1,表示第一次复制。

主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。

有个地方需要注意, FULLRESYNC 响应表示第一次复制采用的全量,也就是说,主库会把当前所有的数据都复制给从库

第二阶段

在第二阶段,主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照 RDB 文件。

具体来说,主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库。从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。这是因为从库可能保存了其他数据,为了避免之前数据的影响,从库需要先把当前数据库清空。

在主从同步过程中,仍然可以正常接收请求。否则,Redis 的服务就被中断了。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。

第三阶段

最后,也就是第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成 RDB 文件发送后,就会把此 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。这样一来主从库就实现同步了。

1.2 主从级联模式分担全量复制时的主库压力

通过分析主从库间第一次数据同步的过程,你可以看到,一次全量复制,对于主库来说,需要完成两个耗时的操作:生成 RDB 文件和传输 RDB 文件。

如果从库数量很多,而且都要和主库进行全量复制的话,就会导致主库忙于 fork 子进程生成 RDB 文件,进行全量数据同步。fork 这个操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求速度变慢。此外,传输 RDB 也会占用主库的网络带宽,同样会给主库的资源使用带来压力。那么,有没有好的解决方法可以分担主库压力呢?

其实是有的,这就是“主 - 从 - 从”模式。

上面介绍的主从库模式中,所有的从库都是和主库连接,所有的全量复制也都是和主库进行的。现在,我们可以通过“主 - 从 - 从”模式将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上

简单来说,我们在部署主从集群的时候,可以手动选择一个从库(比如选择资源配置较高的从库),用于级联其他的从库。然后,我们可以再选择一些从库(例如三分之一的从库),在这些从库上执行如下命令,让它们和刚才所选的从库,建立起主从关系。

replicaof 所选从库的IP 6379

这样一来,这些从库就会知道,在进行同步时,不用再和主库进行交互了,只要和级联的丛库进行写操作同步就行了,这就可以减轻主库上的压力,如下图所示:
在这里插入图片描述
到这里,我们了解了主从库间通过全量复制实现数据同步的过程,以及通过“主 - 从 - 从”模式分担主库同步压力的方式。那么,一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续收到的写操作再同步给从库,这个过程也称基于长连接的命令传播,可以避免频繁建立连接的开销。

在这个过程中存在风险点,最常见的是网络断连或阻塞。如果网络断连,主从库之间就无法进行命令传播了,从库的数据自然也就没办法和主库保持一致了,客户端也就可能从从库中读到旧数据。

1.3 主从库间网络断了怎么办?

早 Redis 2.8 之前,如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量同步,开销非常大。

从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步。增量复制只会把主从库网络断连期间主库收到的命令,同步给从库。

增量复制时,主从库之间是怎么保持同步的?是基于 repl_backlog_buffer 这个缓冲区。

当主从库断连后,主库会把断连期间收到的写操作,写入 replication buffer,同时也会把这些操作也写入 repl_backlog_buffer 缓冲区。

repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置

刚开始的时候,主库与从库的写读位置在一起,这算是它们的起始位置。随着主库不断接收新的写操作,它们在缓冲区中的写位置会逐步偏离起始位置,我们通常用偏移量来衡量这个便宜距离的大小,对主库来说,对应的偏移量就是 master_repl_offset。主库接收的新写操作越多,这个值就越大。

同样,从库在复制写完操作命令后,它在缓冲区中的读位置也开始逐步偏移刚才的起始位置,此时,从库已复制的偏移量 slave_repl_offset 也在不断增加。正常情况下,这两个便宜量基本相等。
在这里插入图片描述
主从库的连接回复之后,首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发送给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset 之间的差距。

在网络断连阶段,主库可能会收到新的写操作命令,所以,一般来说,master_repl_offset 会大于 slave_repl_offset。此时,主库只用把 master_repl_offset 和 slave_repl_offset 之间的命令操作同步给从库就行。

就像刚刚示意图的中间部分,主库和从库之间相差了 put d e 和 put d f 两个操作,在增量复制时,主库只需要把它们同步给从库就行了。

说到这里,我们再借助一张图,回顾下增量复制的流程。
在这里插入图片描述
不过,因为 repl_backlog_buffer 是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新的写操作覆盖了,这会导致主从库间的数据不一致

我们要想办法避免这一情况,一般而言我们可以调整 repl_backlog_size 这个参数。缓冲空间的计算公式是: 缓冲空间大小 = 主库写入命令速度 * 操作大小 - 从库网络传输命令速度 * 操作大小。 在实际应用中,考虑到可能存在一些突发的请求压力,通常把这个缓存空间扩大一倍,即 repl_backlog_size = 缓冲空间大小 * 2,这也就是 repl_backlog_size 的最终值。

举个例子,如果主库每秒写入 2000 个操作,每个操作的大小为 2KB,网络每秒能传输 1000 个操作,那么,有 1000 个操作需要缓冲起来,这就至少需要 2MB 的缓冲空间。否则,新写的命令就会覆盖掉就操作了。为了应对可能的突发压力,我们最终把 repl_backlog_size 设为 4MB。

这样一来,增量复制时主从库的数据风险不一致风险就降低了。不过,如果并发请求量非常大,连两倍的缓冲空间都存不下新操作的话,此时,主从库数据仍然可能不一致。

针对这种情况,一方面,你可以根据 Redis 所在服务器的内存资源再适当增加 repl_backlog_size 值,比如说设置成缓冲空间大小的 4 倍,另外一方面,你可以考虑使用切片集群来分担单个主库的请求压力。

1.4 总结

总体来说,Redis 的主从库同步的基本原理,有三种模式:全量同步、基于长链接的命令传播,以及增量同步。

全量复制虽然耗时,但是对于从库来说,如果是第一次同步,全量复制是无法避免的,所以,建议一个 Redis 的数据量不要太大,一个实例的大小在几 GB 级别比较合适,这样可以减少 RDB 文件生成、传输和重新加载的开销。另外,为了避免多个从库同时和主库进行全量复制,给主库过大压力,可以采用“主 - 从 - 从”级联模式,来缓解主库压力。

2. 哨兵机制:主库挂了,如何不间断服务?

我们知道在主从集群模式下,如果从库发生故障,客户端可以继续向主库或其他从库发送请求,但如果主库发生故障了,那就直接会影响到从库的同步,因为从库没有相应的主库可以进行数据复制操作。

如果客户端发送的都是读操作请求,那还可以由从库继续服务。但是,一旦有写操作请求了,此时没有实例可以来服务客户端的写请求,如下所示:
在这里插入图片描述
无论是写服务中断,还是从库无法进行数据同步,都是不能接受的。所以,若果主库挂了,我们需要运行一个新主库,比如把一个从库切换为主库。这设计到三个问题:

  1. 主库真的挂了吗?
  2. 该选哪个从库作为主库?
  3. 怎么把新主库的相关信息通知给从库和客户端呢?

在 Redis 主从集群中,哨兵机制是实现主从库自动切换的关键机制。

2.1 哨兵机制的基本流程

哨兵其实是一个运行在特殊环境下的 Redis 进程,主从库实例运行的同时,它也在运行。哨兵主要负责三个任务:监控、选主和通知。

监控是指哨兵进程在运行时,周期性地给所有主从库发送 PING 命令,检测它们是否仍然在线。如果从库没有在规定时间内没有响应哨兵的 PING 命令,哨兵就会把标记为“下线状态”;同样,如果主库没有在规定时间内响应哨兵的 PING 命令,哨兵就会判定主库下线。

然后就开始自动切换主库的流程,这是哨兵的第二个任务,选主。主库挂了后,哨兵就需要从多个从库中,按照一定的规则选择一个从库,把它作为新主库。这一步完成后,现在的集群里就有了新主库。

然后,哨兵会执行最后一个任务:通知。在执行通知任务时,哨兵会把新主库的连接信息发送给其他从库,让它们执行 replicaof 命令,和新主库建立连接,并进行数据复制。同时,哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发到新主库上。
在这里插入图片描述
在这三个任务中,通知任务相对来水比较简单,哨兵只需要把新主库信息发送给从库和客户端,让它们和新主库建立连接就行,并不涉及决策的逻辑。但是在监控和选主的任务中,哨兵需要做出两个决策:

  1. 在监控任务中,哨兵需要判断主库是否处于下线状态
  2. 在选主任务中,哨兵需要选择哪个从库作为主库

接下来先看下哨兵如何判断主库的下线状态。

需要先知道,哨兵对于主库的下线判断有“主观下线”和“客观下线”两种。那么,为什么会存在两种判断?它们的区别和联系是什么?

2.2 主观下线和客观下线

先来解释下什么是主观下线

哨兵进程会使用 PING 命令检查自己和主从库的网络连接情况,用来判断实例的状态。如果哨兵发现主库或从库对 PING 命令的响应超时,那么,哨兵就会把它标记为“主观下线”。

此时,若检测的是从库,哨兵只需简单地把它标记为“主观下线”就行了,因为从库的下线影响一般不大,集群的对外服务不会间断。

但是,如果检测的是主库,哨兵不能简单的标记为“主观下线”,就开启主从切换。因为有可能是哨兵误判了,其实主库并没故障。

因为一旦启动了主从切换,后续的选主、新主从库间的数据同步和通知操作都会带来额外的计算和通信开销。

为了避免这些不必要的麻烦,要注意避免误判的情况。

误判一般发生在集群网络压力较大、网络拥堵,或者是主库本身压力较大的情况下。

如何减少误判呢? 在日常生活这,我们要对一些重要的事情做判断的时候,经常和家人或朋友一起商量下,然后再做决定。哨兵机制也是类似的,它通常采用多实例组成的集群模式进行部署,这也被称为哨兵集群。引入多个哨兵一起来判断,就可以避免单个哨兵因自身网络状况不好,而误判主库下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由他们一起做决策,误判率也能极大降低。

在判断主库是否下线时,不能由一个哨兵说了算,只有大多数的哨兵实例,都判断主库已经“主观下线”了,主库才会被标记为“客观下线”。这个判断的原则是:少数服从多数。同时,这会进一步触发哨兵开始主从切换流程。

我们举个例子,如下图所示, Redis 主从集群有一个主库、三个从库,还有三个哨兵实例。
在图片的左边:

  • 哨兵 2 判断主库“主观下线”
  • 哨兵 1、3 却判定主库是上线状态
  • 此时,主库仍然会被判定处于上线状态。

在图片右边:

  • 哨兵 1、2 判断主库“主观下线”
  • 此时,即使哨兵 3 判定主库是上线状态,主库也被标记为“客观下线”了

在这里插入图片描述
简而言之,“客观下线”的标准是,当有 N 个哨兵实例,最好要有 N/2 + 1 个实例判断主库为“主观下线”,才能最终判定主库为“客观下线”。

2.3 如何选定新主库?

哨兵选择新主库的过程称为“筛选 + 打分”。

简单来说,我们在多个从库中,先按照一定的筛选条件,把不符合条件的从库去掉。然后,再按照一定的规则,给剩下的从库逐个打分,将得分最高的从库选为新主库,如下所示:
在这里插入图片描述

一定的筛选条件

首先,我们要把当前下线的从库筛掉。我们肯定要先保证所选的从库仍然在线。不过在选主时从库正常在线,只能表示从库的现状良好,并不代表它就是最适合做新主库的。

其次,除了要检查从库的当前在线状态,还要判断它之前的网络连接状态。若从库总是和主库断连,而且断连次数超过了一定的阈值,就可以把这个从库筛掉了。

具体如何判断之前的网络连接状态呢?使用 Redis 配置项 down-after-milliseconds * 10。其中,down-after-milliseconds 是我们认定主从库断连的最大连接超时时间。如果在 down-after-milliseconds 毫秒内,主从节点都没有通过网络联系上,我们就认为主从节点断连了。如果发生断连的次数超过了10次,就说明这个从库的网络状况不好,不适合作为新主库。

好了,这样我们就过滤掉了不适合做新主库的从库了,完成了筛选工作。

一定的规则

接下来,我们要给剩下的从库打分了。分别按照三个规则依次打分,这三个规则是从库优先级、从库复制进度、从库 ID 号。只要在某一轮中,有从库得分最高,那么它就是主库了。如果没有出现得分最高的从库,那么就继续进行下一轮。

第一轮:优先级最高的从库得分高

可以通过 slave-priority 配置项,给不同的从库设置优先级。比如,手动给内存大的实例设置一个高优先级。在选主时,哨兵就会给优先级高的从库打高分,那么它就是新主库了。如果从库的优先级一样,那么哨兵就开始第二轮打分。

第二轮:和旧主库同步程度最接近的从库得分高
我们知道主从库同步时有个命令传播的过程。在这个过程中,主库会用 master_repl_offser 记录当前的最新写操作在 repl_backlog_buffer 中的位置,而从库会用 slave_repl_offset 这个值记录当前的复制进度。

此时,我们想要找的从库,它的 slave_repl_offser 需要最接近 master_repl_offset。如下图所示,旧主库的 master_repl_offset 是 1000,从库 1、2、3 的 salve_repl_offset 分别是 950、990、900,那么从库 2 就应该被选为主库。
在这里插入图片描述
当然,如果从库的 slave_repl_offset 值大小是一样的,我们就需要给它们进行第三轮打分了。

第三轮:ID 号小的从库得分高

每个从库实例都会有一个 ID,这个 ID 就类似与从库编号。目前,Redis 在选主库的时候,有一个默认规定: 在优先级和复制进度都相同的情况下,ID 号最小的从库得分最高,会被选为新主库。

到这里,新主库就被选出来了。

小结

我们在回顾下这个流程。首先,哨兵会按照在线状态、网络状态,过滤掉一部分不符合要求的从库,然后,依次按照优先级、复制进度、ID 号大小,再对剩余的从库进行打分,只要有得分高的从库出现,就把它选为新主库。

3. 哨兵集群:哨兵挂了,主从库还能切换吗?

上一节,我们学习了哨兵机制,它可以实现主从库的自动切换。通过部署多个哨兵实例,就形成了一个哨兵集群。哨兵集群中的多个实例共同判断,可以降低对主库下线的误判。

还需要考虑一个问题:如果有哨兵实例在运行时发生了故障,主从库还能正常切换吗?

实际上,一旦多个实例组成了哨兵集群,即使有哨兵实例出现故障挂掉了,其他哨兵还能继续协作完成主从库的切换工作,包括判定主库是不是处于下线状态、选新主库、通知从库和客户端。

如果你部署过哨兵集群的话就会知道,在配置哨兵的信息时,我们只需要用到下面的配置,设置主库 IP端口,并没有配置其他的哨兵信息。

sentinel monitor <master-name> <ip> <redis-port> <quorum>

这些哨兵实例既然都不知道彼此的地址,又是怎么组成集群的呢?要弄明白这个问题 ,我们要学习下哨兵集群的组成和运行机制了。

3.1 基于 pub/sub 机制的哨兵集群组成

哨兵实例之间可以相互发现,要归功于 Redis 的 pub/sub 机制,也就是发布 / 订阅机制。

哨兵只要和主库建立起了连接,就可以在主库上发布消息了,比如它发布自己的连接信息(IP 和端口)。同时,它也可以从主库上订阅消息,获得其他哨兵发布的信息。当多个哨兵实例都在主库上做了发布和订阅操作后,它们之间就能知道彼此的 IP 地址和端口。

Redis 以频道的形式区分不同应用的消息,并对这些消息进行分门别类的管理。所谓频道就是消息的类别。当消息类别相同时,它们就属于同一个频道。只有订阅了同一个频道的应用,才能通过发布的消息进行消息交换

在主从集群中,主库上有个名为 “__sentinel__:hello”的频道,不同哨兵就是通过它来相互发现,实现相互通信的。

举个例子。在下图中:

  1. 哨兵 1 把自己的 IP(172.16.19.3) 和端口(26579)发布到 “__sentinel__:hello”频道上。
  2. 哨兵 2、3 订阅了该频道。那么此时,哨兵 2、3 就从可从这个频道直接获取哨兵 1 的 IP 和端口号。
  3. 然后,哨兵 2、3 和哨兵 1 建立网络连接。
  4. 通过这个方式哨兵 2、3 也可以建立网络连接。

这样一来哨兵集群就形成了。它们可以通过网络连接进行通信,比如过主库有没有下线这件事儿进行协商。
在这里插入图片描述
哨兵除了彼此之间建立起连接形成集群外,还需要和从库建立连接。这是因为,在哨兵的监控任务中,它需要对主从库都进行心跳判断,而且在主从库切换完成后,它还需要通知从库,让他们和新主库进行同步。

哨兵是如何知道从库的 IP 地址和端口的呢?
这是通过哨兵向主库发送 INFO 命令来完成的。就像下图所示:

  1. 哨兵 2 给主库发送 INFO 命令,主库接受到这个命令后,就会把从库列表返回给哨兵。
  2. 接着,哨兵就可以根据从库列表中的连接信息,和每个从库建立连接,并在这个连接上持续的对从库进行监控。
  3. 哨兵 1、3 通过相同的方法和从库建立连接。
    在这里插入图片描述
    通过 pub/sub 机制,哨兵之间可以组成集群,同时哨兵又通过 INFO 命令,获取了从库连接信息,也能和从库建立连接,并进行监控了。

但是,哨兵不能只和主、从库连接。因为,主从库切换后,客户端也需要知道新主库的连接信息,才能向新主库发送请求。所以,哨兵还需要完成把新主库的信息告诉各个客户端这个任务。

而且,在实际使用哨兵时,有时会遇到这样的问题:如何在客户端通过监控了解哨兵进行主存切换的过程呢?比如说主从库切换到哪一步了?这其实就是要求,客户端能够获取到哨兵集群在监控、选主、切换这个过程中发生的事件。

此时,我们仍可以通过 pub/sub 机制,来帮助我们完成哨兵和客户端间的信息同步。

3.2 基于 pub/sub 机制的客户端事件通知

每个哨兵实例也提供了 pub/sub 机制,客户端可以从哨兵订阅消息。哨兵提供的消息频道有很多,不同频道包含了主从库切换过程中的不同关键事件。

在这里插入图片描述
知道了这些频道,就可以让客户端从哨兵这里订阅消息了。具体的操作步骤是,客户端读取哨兵的配置文件后,可以获得哨兵的地址和端口,和哨兵建立网络连接。然后,我们可以在客户端执行订阅命令来获取不同的事件消息。

举个例子,你可以执行如下命令,来订阅“所有实例进入客观下线状态的事件”:

SUBSCRIBE +odown

当然,你可以可以执行如下命令,订阅所有的事件:

PSUBCRIBE *

当哨兵把新主库选择出来后,客户端就会看到下面的 switch-master 事件。这个事件表示主库已经切换了,新主库的 IP 地址和端口信息已经有了。这个时候,客户端就可以用这里的新主库地址和端口进行通信了:

Switch-master <master name> <oldip> <oldport> <newip> <newport>

有了这些事件通知,客户端不仅可以在主存切换后得到新主库的连接信息,还可以监控主从库切换过程中发生的各个重要事件。这样,客户端就可以知道主从切换进行到哪一步了,有助于了解切换进度。

还有一个问题就是,主库故障以后,哨兵集群中有多个实例,那么怎么确定由哪个哨兵进行实际的主从切换呢?

3.3 由哪个哨兵执行主从切换

确定由哪个哨兵执行主从切换的过程,和主库“客观下线”的判断过程类似,也是一个投票仲裁的过程。在了解这个过程之前,我们先来看下,判断“客观下线”的仲裁过程。

任何一个实例只要自身判断“主观下线”后,就会给其他实例发送 is-master-down-by-addr 命令。接着,其他实例会根据自己和主库的连接情况,做出 Y 和 N 的响应。

在这里插入图片描述

一个哨兵获得了仲裁所需的赞成票后,就可以标注“客观下线”。这个所需的赞成票数是通过哨兵配置文件中的 quorum 配置项设定的。例如,现有 5 个哨兵,quorum 配置的是 3,那么,一个哨兵需要 3 张赞成票,就可以标记主库为 “客观下线”了。这 3 张赞成票,包括哨兵自己的一张赞成票和另外两个哨兵的赞成票。

此时,这个哨兵就可以给其他哨兵发送命令,表明希望由自己来执行主从切换,并让所有其他哨兵进行投票。这个投票过程为“Leader 选举”。因为最终执行主从切换的哨兵称为 Leader,投票过程就是确定 Leader。

在投票过程中,任何一个想成为 Leader 的哨兵,要满足两个条件:

  1. 第一,拿到半数以上的赞成票
  2. 第二,拿到的票数还需要大于等于哨兵配置文件的 quorum 的值。以3个哨兵为例,假设此时 quorum 为 2,任何一个想成为 Leader 的哨兵只要拿到 2 张赞成票,就可以了。

再画一张图,展示下 3 个哨兵、 quorum 为 2 的选举过程。
在这里插入图片描述

  • T1 时刻, S1 判断主库为“客观下线”,它想称为 Leader,就先给自己投一张赞成票,然后分别向 S2、S3 发送命令,表示要成为 Leader。
  • T2 时刻,S3 判断主库为“客观下线”,它也想成为 Leader,也先给自己投一张赞成票,然后分别向 S1、S2 发送命令,表示要称为 Leader。
  • T3 时刻,S1 收到了 S3 的 Leader 投票请求。因为 S1 已经给自己投了一票,所以它不能再给其他哨兵投赞成票,所以回复 N 表示不同意 S3 成为 Leader。
    同时, S2 收到了 T2 时刻 S3 发送的 Leader 投票请求。因为 S2 之前没有投过票,它会给第一个向他发送投票请求的哨兵恢复 Y,给后续再发送投票请求的哨兵恢复 N。所以在 T3 时刻,S2 同意 S3 称为 Leader。
  • 在 T4 时刻,S2 才收到 S1 发送的投票命令,此时 S2 给 S1 回复 N ,表示不同意 S1 成为 Leader。发生这种情况,是因为 S3 和 S2 之间网络传输正常,而 S1 和 S2 之前的网络传输可能正好堵塞了,导致投票请求传输慢了。
  • 最后,在 T5 时刻,最终 S1 只有一个赞成票,而 S3 有两个赞成票。此时,S3 不仅获得了半数以上的 Leader 赞成票,也达到预设的 quorum 值(quorum 为 2),所以它最终称为了 Leader。接着 S3 开始执行选主操作,而且选定新主库后,会给其他客户端通知新主库信息。

如果 S3 也没有拿到 2 票 Y,那么这轮投票就不会产生 Leader。哨兵集群会等待一段时间,在重新选举。这是因为,哨兵集群能够进行成功投票,很大程度上依赖于选举命令的正常网络传播。如果网络压力较大或有较短的堵塞,就可能导致没有一个哨兵能拿到半数以上的赞成票。所以,等到网络拥塞好转之后,再进行投票选举,成功的概率就会增加。

需要注意的是,如果哨兵集群只有2个实例,此时,一个哨兵要想成为 Leader,必须获得 2 票,而不是 1 票。所以,如果有个哨兵挂掉了,那么此时的集群是无法进行主从库切换的。因此,我们至少会配置 3 个哨兵实例。这一点很重要,你在实际应用时可不能忽略了。

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值