织梦cms做网站流程,做网站的域名多少钱,模仿采集网站生成网页,黑龙江省建设工程网1.数据同步#xff1a;主从库如何实现数据一致#xff1f;
前面我们学习了 AOF 和 RDB#xff0c;如果 Redis 发生了宕机#xff0c;它们可以分别通过回放日志和重新读入 RDB 文件的方式恢复数据#xff0c;从而保证尽量较少丢失数据#xff0c;提升可靠性。
不过…1.数据同步主从库如何实现数据一致
前面我们学习了 AOF 和 RDB如果 Redis 发生了宕机它们可以分别通过回放日志和重新读入 RDB 文件的方式恢复数据从而保证尽量较少丢失数据提升可靠性。
不过即使使用了这两种方法也依然存在服务不可以的问题。比如说只运行一个 Redis 实例如果这个实例宕机了它在恢复期间是无法服务新来的数据操作请求的。
我们常说的 Redis 具有高可靠其实有两层含义一是数据尽量减少丢失而是服务尽量减少中断。AOF 和 RDB 保证了前者而对于后者Redis 的做法是增加副本冗余量。将一份数据同时保存在多个实例上。这样即使有一个实例出现了故障需要果断时间才能恢复其他实例也可以对外提供服务不影响业务使用。
Redis 提供了主从库模式以保证数据副本的一致主从库之间采用的是读写分离的方式。
读操作主库、从库都可以接收写操作首先到主库执行然后主库将写操作同步给从库。 为什么采用读写分离的方式 设想一下如果不管是主库还是从库都能接收客户端的写操作那么一个直接的问题就是如果客户端对同一个数据例如 k1前后修改了三次每一次修改请求都发送到不同的实例上执行那么这个数据在这三个实例上的副本就不一致了。在读取这个数据的时候就可能读到旧值。 如果我们非要保持这个数据在三个实例上一致就涉及加锁、实例间协商是否完成修改等一些列操作但这会带来巨额的开销当然是不能接受的。 而主从模式一旦采用了读写分离所有数据的修改只会在主库上进行不用协调三个实例。主库有了最新的数据后会同步给从库这样主从库的数据就是一致的。 主从库同步是如何完成的主库是一次性传给从库还是分批同步要是主从库网络断连了数据还能保持一致吗
我们先来看下主从库间的第一次同步是如何进行的这也是 Redis 实例建立主从库模式后的规定动作。
1.1 主从库间如何进行第一次同步
当我们启动多个 Redis 实例的时候它们之间就可以通过 replicaof Redis 5.0 之前使用 slaveof命令形成主从库的关系之后会按照三个阶段完成数据的第一次同步。
例如现在有实例 1ip172.16.19.3和实例 2ip172.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. 哨兵机制主库挂了如何不间断服务
我们知道在主从集群模式下如果从库发生故障客户端可以继续向主库或其他从库发送请求但如果主库发生故障了那就直接会影响到从库的同步因为从库没有相应的主库可以进行数据复制操作。
如果客户端发送的都是读操作请求那还可以由从库继续服务。但是一旦有写操作请求了此时没有实例可以来服务客户端的写请求如下所示 无论是写服务中断还是从库无法进行数据同步都是不能接受的。所以若果主库挂了我们需要运行一个新主库比如把一个从库切换为主库。这设计到三个问题
主库真的挂了吗该选哪个从库作为主库怎么把新主库的相关信息通知给从库和客户端呢
在 Redis 主从集群中哨兵机制是实现主从库自动切换的关键机制。
2.1 哨兵机制的基本流程
哨兵其实是一个运行在特殊环境下的 Redis 进程主从库实例运行的同时它也在运行。哨兵主要负责三个任务监控、选主和通知。
监控是指哨兵进程在运行时周期性地给所有主从库发送 PING 命令检测它们是否仍然在线。如果从库没有在规定时间内没有响应哨兵的 PING 命令哨兵就会把标记为“下线状态”同样如果主库没有在规定时间内响应哨兵的 PING 命令哨兵就会判定主库下线。
然后就开始自动切换主库的流程这是哨兵的第二个任务选主。主库挂了后哨兵就需要从多个从库中按照一定的规则选择一个从库把它作为新主库。这一步完成后现在的集群里就有了新主库。
然后哨兵会执行最后一个任务通知。在执行通知任务时哨兵会把新主库的连接信息发送给其他从库让它们执行 replicaof 命令和新主库建立连接并进行数据复制。同时哨兵会把新主库的连接信息通知给客户端让它们把请求操作发到新主库上。 在这三个任务中通知任务相对来水比较简单哨兵只需要把新主库信息发送给从库和客户端让它们和新主库建立连接就行并不涉及决策的逻辑。但是在监控和选主的任务中哨兵需要做出两个决策
在监控任务中哨兵需要判断主库是否处于下线状态在选主任务中哨兵需要选择哪个从库作为主库
接下来先看下哨兵如何判断主库的下线状态。 需要先知道哨兵对于主库的下线判断有“主观下线”和“客观下线”两种。那么为什么会存在两种判断它们的区别和联系是什么 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 把自己的 IP172.16.19.3 和端口26579发布到 “__sentinel__:hello”频道上。哨兵 2、3 订阅了该频道。那么此时哨兵 2、3 就从可从这个频道直接获取哨兵 1 的 IP 和端口号。然后哨兵 2、3 和哨兵 1 建立网络连接。通过这个方式哨兵 2、3 也可以建立网络连接。
这样一来哨兵集群就形成了。它们可以通过网络连接进行通信比如过主库有没有下线这件事儿进行协商。 哨兵除了彼此之间建立起连接形成集群外还需要和从库建立连接。这是因为在哨兵的监控任务中它需要对主从库都进行心跳判断而且在主从库切换完成后它还需要通知从库让他们和新主库进行同步。
哨兵是如何知道从库的 IP 地址和端口的呢 这是通过哨兵向主库发送 INFO 命令来完成的。就像下图所示
哨兵 2 给主库发送 INFO 命令主库接受到这个命令后就会把从库列表返回给哨兵。接着哨兵就可以根据从库列表中的连接信息和每个从库建立连接并在这个连接上持续的对从库进行监控。哨兵 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 的哨兵要满足两个条件
第一拿到半数以上的赞成票第二拿到的票数还需要大于等于哨兵配置文件的 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 个哨兵实例。这一点很重要你在实际应用时可不能忽略了。