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

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

前面我们学习了 AOF 和 RDB,如果 Redis 发生了宕机,它们可以分别通过回放日志和重新读入 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. 哨兵集群:哨兵挂了,主从库还能切换吗?

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

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

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

如果你部署过哨兵集群的话就会知道,在配置哨兵的信息时,我们只需要用到下面的配置,设置主库 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 个哨兵实例。这一点很重要,你在实际应用时可不能忽略了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/352906.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3 + antd 封装动态表单组件(二)

传送带&#xff1a; vue3 antd 封装动态表单组件&#xff08;一&#xff09; 前置条件&#xff1a; vue版本 v3.3.11 ant-design-vue版本 v4.1.1 vue3 antd 封装动态表单组件&#xff08;一&#xff09;是基础版本&#xff0c;但是并不好用&#xff0c; 因为需要配置很多表…

VR拍摄+制作

1.VR制作需要的图片宽高是2:1&#xff0c;需要360✖️180的图片&#xff0c;拍摄设备主要有两种&#xff1a; 1&#xff09;通过鱼眼相机拍摄&#xff0c;拍摄一组图片&#xff0c;然后通过PTGui来合成(拍摄复杂) 2&#xff09;全景相机&#xff0c;一键拍摄直接就能合成需要的…

Android颜色选择器

Android颜色选择器&#xff0c;弹框提示选择颜色。效果如图。点击或者滑动圆环和底部横向渐变色调整颜色&#xff0c;中间圆圈的颜色就是最终选中的颜色。点击圆圈确认颜色。 使用 //颜色选择Dialogprivate void showColorPickDialog(int position, int colorInt){ColorPickerD…

数据结构(绪论+算法的基本概念)

文章目录 一、绪论1.1、数据结构的基本概念1.2、数据结构三要素1.2.1、逻辑结构1.2.2、数据的运算1.2.3、物理结构&#xff08;存储结构&#xff09;1.2.4、数据类型和抽象数据类型 二、算法的基本概念2.1、算法的特性2.2、“好”算法的特质2.2.1、算法时间复杂度2.2.2、算法空…

【Linux】:线程安全的单例模式

线程安全的单例模式 一.STL和智能指针的安全二.单例模式1.基本概念2.懒汉和饿汉的实现方式 三.常见的其它锁四.读者写者模型 一.STL和智能指针的安全 1.STL中的容器是否是线程安全的? 不是. 原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性…

[SwiftUI]系统弹窗和自定义弹窗

一、系统弹窗 在 SwiftUI 中&#xff0c;.alert 是一个修饰符&#xff0c;用于在某些条件下显示一个警告对话框。Alert 可以配置标题、消息和一系列的按钮。每个按钮可以是默认样式、取消样式&#xff0c;或者是破坏性的样式&#xff0c;它们分别对应不同的用户操作。 1.Aler…

Spring 的存储和获取Bean

文章目录 获取 Spring 上下文对象的方式存储 Bean 对象的方式类注解配置扫描路径&#xff08;必须&#xff09;Controller&#xff08;控制器存储&#xff09;Service&#xff08;服务&#xff09;Repository&#xff08;持久层&#xff09;Component&#xff08;工具&#xff…

【Spring】Spring简介、IOC、DI

目录 Spring简介 Spring Framework五大功能模块 IOC容器 IOC思想 IOC容器在Spring中的实现 基于XML管理bean 配置bean 获取bean 依赖注入之setter注入 依赖注入之构造器注入 特殊值处理 字面量赋值 null值 xml实体 CDATA节 为类类型属性赋值 为数组类型属性赋值 为集合类型属性…

JavaScript 学习笔记(JS进阶 Day1)

「写在前面」 本文为 b 站黑马程序员 pink 老师 JavaScript 教程的学习笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。推荐先按顺序阅读往期内容&#xff1a; 1. JavaScript 学习笔记&#xff08;Day1&#xff09; 2. JavaSc…

适用于 Windows 11 的 12 个最佳免费 PDF 编辑器

除了绘图等基本功能外&#xff0c;一些适用于 Windows 11 的免费 PDF 编辑器还具有 AI、OCR 识别和书签等高级功能。 我们的列表包含易于立即下载的 PDF 编辑软件工具。 这些工具不仅可以帮助转换 PDF、编辑、上传、删除、裁剪、分割、提取等。 PDF 是指便携式文档格式&…

单片机学习笔记---独立按键控制LED亮灭

直接进入正题&#xff01; 今天开始我们要学习一个新的模块&#xff1a;独立按键&#xff01; 先说独立按键的内部结构&#xff1a; 它相当于一种电子开关&#xff0c;按下时开关接通&#xff0c;松开时开关断开&#xff0c;实现原理是通过轻触按键内部的金属弹片受力弹动来实…

剧本杀小程序开发:打造沉浸式推理体验

随着社交娱乐形式的多样化&#xff0c;剧本杀逐渐成为年轻人喜爱的聚会活动。而随着技术的发展&#xff0c;剧本杀小程序的开发也成为了可能。本文将探讨剧本杀小程序开发的必要性、功能特点、开发流程以及市场前景。 一、剧本杀小程序开发的必要性 剧本杀是一种角色扮演的推…

鸿蒙端云一体化简单项目

文章目录 前言端云一体化服务端客户端云数据库总结 一、前言 鸿蒙系统在不断地成熟&#xff0c;现在有了鸿蒙端云一体化开发模式。什么是端云一体化呢&#xff0c;简单点就是你原本是客户端开发的&#xff0c;项目中只是客户端的代码&#xff0c;端云一体化呢&#xff0c;就…

【MyBatis】#{} 和 ${}

目录 1. #{} 使用示例&#xff1a; 2. ${} 使用示例&#xff1a; SQL注入 使用#{}的情况&#xff1a; 使用${}的情况&#xff1a; MyBatis是一种用于Java语言的持久层框架&#xff0c;它简化了数据库操作的过程。在MyBatis中&#xff0c;我们经常会看到两种不同的参数占…

华为云WAF,开启web网站的专属反爬虫防护罩

背景 从保护原创说起 作为一个原创技术文章分享博主&#xff0c;日常除了Codeing就是总结Codeing中的技术经验。 之前并没有对文章原创性的保护意识&#xff0c;直到在某个非入驻的平台看到了我的文章&#xff0c;才意识到&#xff0c;辛苦码字、为灵感反复试验创作出来的文…

苹果macOS 恶意软件家族被曝光:通过破解软件分发,可窃取敏感信息

卡巴斯基安全实验室近日发布博文&#xff0c;发现了一种针对苹果 macOS 设备的新型恶意软件家族&#xff0c;并提醒苹果 Mac 用户谨慎下载破解软件。 报告称这种新型恶意软件家族高度复杂&#xff0c;主要伪装成为各种知名 macOS 软件的破解版分发&#xff0c;用户下载恶意 PKG…

ISO 14229和UDS:汽车诊断的黄金标准

UDS简介&#xff1a; UDS是Unified Diagnostic Services的缩写&#xff0c;全名统一诊断服务。它是一种用于汽车电子控制单元&#xff08;ECU&#xff09;之间进行诊断和通信的标准协议&#xff0c;属于ISO 14229标准的一部分。 UDS的起源和背景&#xff1a; UDS的起源可以追…

HarmonyOS 鸿蒙应用开发 (七、HTTP网络组件 axios 介绍及封装使用)

在HarmonyOS应用开发中&#xff0c;通过HTTP访问网络&#xff0c;可以使用官方提供的ohos.net.http模块。但是官方提供的直接使用不太好使用&#xff0c;需要封装下才好。推荐使用前端开发中流行的axios网络客户端库&#xff0c;如果是前端开发者&#xff0c;用 axios也会更加顺…

Java笔记(死锁、线程通信、单例模式)

一、死锁 1.概述 死锁 : 死锁是指两个或两个以上的进程在执行过程中&#xff0c;由于竞争资源或者由于彼此通信而造成的一种阻塞的现象&#xff0c;若无外力作用&#xff0c;它们都将无法往下执行。此时称系统处于死锁状态或系统产生了死锁&#xff0c;这些永远在互相等待的进…

vusui css 使用,简单明了 适合后端人员 已解决

vusui-cssopen in new window 免除开发者繁复的手写 CSS 样式&#xff0c;让 WEB 前端开发更简单、灵活、便捷&#xff01;如果喜欢就点个 ★Staropen in new window 吧。 移动设备优先&#xff1a; vusui-css 包含了贯穿于整个库的移动设备优先的样式。浏览器支持&#xff1a…