分布式系统的“脑裂”到底是个什么玩意?

article/2025/9/21 2:51:39

目前大多数项目都在往分布式上发展,一旦系统采用分布式系统,便会引入更多复杂场景和解决方案。比如,当你在系统中使用了Elasticsearch、ZooKeeper集群时,你是否了解过集群的“脑裂”现象?又是否知道它们是如何解决脑裂问题的?

如果这些都还未了解,那么你对分布式的了解过于表象了,推荐你读一读这篇文章。

下面就以zookeeper为例,带大家了解一下分布式系统中的脑裂现象及如何解决。

什么是脑裂?

在Elasticsearch、ZooKeeper这些集群环境中,有一个共同的特点,就是它们有一个“大脑”。比如,Elasticsearch集群中有Master节点,ZooKeeper集群中有Leader节点。

集群中的Master或Leader节点往往是通过选举产生的。在网络正常的情况下,可以顺利的选举出Leader(后续以Zookeeper命名为例)。但当两个机房之间的网络通信出现故障时,选举机制就有可能在不同的网络分区中选出两个Leader。当网络恢复时,这两个Leader该如何处理数据同步?又该听谁的?这也就出现了“脑裂”现象。

通俗的讲,脑裂(split-brain)就是“大脑分裂”,本来一个“大脑”被拆分成两个或多个。试想,如果一个人有多个大脑,且相互独立,就会导致人体“手舞足蹈”,“不听使唤”。

了解了脑裂的基本概念,下面就以zookeeper集群的场景为例,来分析一下脑裂的发生。

zookeeper集群中的脑裂

我们在使用zookeeper时,很少遇到脑裂现象,是因为zookeeper已经采取了相应的措施来减少或避免脑裂的发生,这个后面会讲到Zookeeper的具体解决方案。现在呢,先假设zookeeper没有采取这些防止脑裂的措施。在这种情况下,看看脑裂问题是如何发生的。

现有6台zkServer服务组成了一个集群,部署在2个机房:

在这里插入图片描述

正常情况下,该集群只有会有个Leader,当Leader宕掉时,其他5个服务会重新选举出一个新的Leader。

如果机房1和机房2之间的网络出现故障,暂时不考虑Zookeeper的过半机制,那么就会出现下图的情况:
在这里插入图片描述

也就是说机房2的三台服务检测到没有Leader了,于是开始重新选举,选举出一个新Leader来。原本一个集群,被分成了两个集群,同时出现了两个“大脑”,这就是所谓的“脑裂”现象。

由于原本的一个集群变成了两个,都对外提供服务。一段时间之后,两个集群之间的数据可能会变得不一致了。当网络恢复时,就面临着谁当Leader,数据怎么合并,数据冲突怎么解决等问题。

当然,上面的过程只是我们假设Zookeeper不做任何预防脑裂措施时会出现的问题。那么,针对脑裂问题,Zookeeper是如何进行处理的呢?

Zookeeper的过半原则

防止脑裂的措施有多种,Zookeeper默认采用的是“过半原则”。所谓的过半原则就是:在Leader选举的过程中,如果某台zkServer获得了超过半数的选票,则此zkServer就可以成为Leader了。

底层源码实现如下:

public class QuorumMaj implements QuorumVerifier {int half;// QuorumMaj构造方法。// 其中,参数n表示集群中zkServer的个数,不包括观察者节点public QuorumMaj(int n){this.half = n/2;}// 验证是否符合过半机制public boolean containsQuorum(Set<Long> set){// half是在构造方法里赋值的// set.size()表示某台zkServer获得的票数return (set.size() > half);}
}

上述代码在构建QuorumMaj对象时,传入了集群中有效节点的个数;containsQuorum方法提供了判断某台zkServer获得的票数是否超过半数,其中set.size表示某台zkServer获得的票数。

上述代码核心点两个:第一,如何计算半数;第二,投票属于半数的比较。

以上图6台服务器为例来进行说明:half = 6 / 2 = 3,也就是说选举的时候,要成为Leader至少要有4台机器投票才能够选举成功。那么,针对上面2个机房断网的情况,由于机房1和机房2都只有3台服务器,根本无法选举出Leader。这种情况下整个集群将没有Leader。

在这里插入图片描述

在没有Leader的情况下,会导致Zookeeper无法对外提供服务,所以在设计的时候,我们在集群搭建的时候,要避免这种情况的出现。

如果两个机房的部署请求部署3:3这种状况,而是3:2,也就是机房1中三台服务器,机房2中两台服务器:
在这里插入图片描述

在上述情况下,先计算half = 5 / 2 = 2,也就是需要大于2台机器才能选举出Leader。那么此时,对于机房1可以正常选举出Leader。对于机房2来说,由于只有2台服务器,则无法选出Leader。此时整个集群只有一个Leader。

对于上图,颠倒过来也一样,比如机房1只有2台服务器,机房2有三台服务器,当网络断开时,选举情况如下:
在这里插入图片描述

Zookeeper集群通过过半机制,达到了要么没有Leader,要没只有1个Leader,这样就避免了脑裂问题。

对于过半机制除了能够防止脑裂,还可以实现快速的选举。因为过半机制不需要等待所有zkServer都投了同一个zkServer就可以选举出一个Leader,所以也叫快速领导者选举算法。

新旧Leader争夺

通过过半原则可以防止机房分区时导致脑裂现象,但还有一种情况就是Leader假死。

假设某个Leader假死,其余的followers选举出了一个新的Leader。这时,旧的Leader复活并且仍然认为自己是Leader,向其他followers发出写请求也是会被拒绝的。

因为ZooKeeper维护了一个叫epoch的变量,每当新Leader产生时,会生成一个epoch标号(标识当前属于那个Leader的统治时期),epoch是递增的,followers如果确认了新的Leader存在,知道其epoch,就会拒绝epoch小于现任leader epoch的所有请求。

那有没有follower不知道新的Leader存在呢,有可能,但肯定不是大多数,否则新Leader无法产生。ZooKeeper的写也遵循quorum机制,因此,得不到大多数支持的写是无效的,旧leader即使各种认为自己是Leader,依然没有什么作用。

ZooKeeper集群节点为什么要部署成奇数

上面讲了过半原则,由于Zookeeper默认采用的就是这种策略,那就带来另外一个问题。集群的数量设置为多少合适呢?而我们所看到的Zookeeper节点数一般都是奇数,这是为什么呢?

首先,只要集群中有过半的机器是正常工作的,那么整个集群就可对外服务。那么我们列举一些情况,来看看在这些情况下集群的容错性。

如果有2个节点,那么只要挂掉1个节点,集群就不可用了。此时,集群对的容忍度为0;

如果有3个节点,那么挂掉1个节点,还有剩下2个正常节点,超过半数,可以重新选举,正常服务。此时,集群的容忍度为1;

如果有4个节点,那么挂掉1个节点,剩下3个,超过半数,可以重新选举。但如果再挂掉1个,只剩下2个,就无法正常选举和服务了。此时,集群的容忍度为1;

依次类推,5个节点,容忍度为2;6个节点容忍度同样为2;

既然3个节点和4个节点、5个节点和6个节点,也就是2n和2n-1的容忍度是一样的,都是n-1。那么,为了节省资源,为了更加高效(更多节点参与选举和通信),为什么不少一个节点呢?这就是为什么集群要部署成奇数的原因。

解决脑裂的常见方法

上面提到了Zookeeper使用的过半原则,这里再把解决脑裂问题的场景方式总结一下。

方法一,Quorums(法定人数)方式

比如3个节点的集群,Quorums = 2,也就是说集群可以容忍1个节点失效,这时候还能选举出1个lead,集群还可用。比如4个节点的集群,它的Quorums = 3,Quorums要超过3,相当于集群的容忍度还是1,如果2个节点失效,那么整个集群还是无效的。这是ZooKeeper防止“脑裂”默认采用的方法。

方法二,添加心跳线

集群中采用多种通信方式,防止一种通信方式失效导致集群中的节点无法通信。

比如,添加心跳线。原来只有一条心跳线路,此时若断开,则接收不到心跳报告,判断对方已经死亡。若有2条心跳线路,一条断开,另一条仍然能够接收心跳报告,能保证集群服务正常运行。心跳线路之间也可以 HA(高可用),这两条心跳线路之间也可以互相检测,若一条断开,则另一条马上起作用。正常情况下,则不起作用,节约资源。

方法三,启动磁盘锁定方式。

使用磁盘锁的形式,保证集群中只能有一个Leader获取磁盘锁,对外提供服务,避免数据错乱发生。但是,也会存在一个问题,若该Leader节点宕机,则不能主动释放锁,那么其他的Follower就永远获取不了共享资源。于是有人在HA中设计了"智能"锁。正在服务的一方只有在发现心跳线全部断开(察觉不到对端)时才启用磁盘锁。平时就不上锁了

方法四,仲裁机制方式。

脑裂导致的后果是从节点不知道该连接哪一台Leader,此时有一个仲裁方就可以解决此问题。比如提供一个参考的IP地址,心跳机制断开时,节点各自ping一下参考IP,如果ping不通,那么表示该节点网络已经出现问题,则该节点需要自行退出争抢资源,释放占有的共享资源,将服务的提供功能让给功能更全面的节点。

以上方式可以同时使用,可以减少集群中脑裂情况的发生,但不能完全保证,比如仲裁机制中2台机器同时宕机,那么此时集群中没有Leader 可以使用。此时就需要人工干预了。

小结

我们经常在说,我们的系统使用了分布式,但我们真的了解分布式中场景的一些场景和解决方案吗?通过本文对脑裂场景的分析及解决方案介绍,你学到了吗?来一起学习吧。

博主简介:《SpringBoot技术内幕》技术图书作者,酷爱钻研技术,写技术干货文章。

公众号:「程序新视界」,博主的公众号,欢迎关注~

技术交流:请联系博主微信号:zhuan2quan


程序新视界

公众号“ 程序新视界”,一个让你软实力、硬技术同步提升的平台,提供海量资料

微信公众号:程序新视界


http://chatgpt.dhexx.cn/article/UWq7RVkr.shtml

相关文章

JDBC编程--jdbc基本操作步骤

JDBC即java数据库互连&#xff0c;是java语言和数据库之间独立于数据库的连接标准API&#xff0c;JDBC从根本上来说是一种规范&#xff0c;具体的实现需要依赖于具体数据库生产商提供jar包【驱动】&#xff0c;提供了统一的接口用于访问不同的底层数据库&#xff0c;允许使用ja…

【Java】JDBC连接数据库详细步骤(面试必备)

系列文章目录 本文旨在详细讲解使用JDBC连接数据库的六个步骤 其中第一步注册驱动的讲解链接&#xff1a;JDBC—MySQL以类加载的方式注册驱动&#xff08;反射&#xff09;_m0_56164356的博客-CSDN博客 // 1、注册驱动// 2、获取连接// 3、获取数据库操作对象// 4、执行sql语句…

JDBC基本步骤

JDBC编程 JDBC即java数据库互连&#xff0c;是java语言和数据库之间独立于数据库的连接标准API&#xff0c;JDBC从根本上来说是 一种规范&#xff0c;具体的实现需要依赖于具体数据库生产商提供jar包【驱动】&#xff0c;提供了统一的接口用于访问不 同的底层数据库&#xff0…

Java使用JDBC步骤

学习目标&#xff1a; Java使用JDBC步骤 学习内容&#xff1a; 1.为项目导入mysql-jdbc的jar包 2.初始化驱动 3.建立与数据库的连接 4.创建Statement 5.执行SQL语句 6.关闭连接 7.使用try-with-resourced的方式自动关闭连接 知识详解&#xff1a; JAVA使用JDBC步骤&#xff…

JDBC之程序编写步骤

当当当当~~~~欢迎大家阅读&#xff0c;今天我们学习JDBC程序编写步骤 以上是JDBC程序编写步骤的流程图 步骤一&#xff1a;导入java.sql包 步骤二&#xff1a;加载并注册驱动程序 java.sql.Driver 接口&#xff1a;是所有 JDBC 驱动程序需要实现的接口&#xff0c;这个接口是…

JDBC具体的操作步骤

目录 1.加载驱动 Driver 2.获得连接 3.执行指令 4.关闭资源 一&#xff1a; 首先先建立一个包为&#xff1a;com.zking.util 包中建立一个类&#xff1a;DBHelper类//帮助类 使用静态代码块加载驱动 2. 定义连接语句 3. 获得连接方法 &#xff1a; 4. 关闭资源:连接c…

浅谈JDBC,JDBC实现的几大步骤

前言 当我们学完数据库之后总觉得数据库和java之间是没有联系的&#xff0c;但是我们还是要去学数据库是为了什么呐&#xff1f;现在就来了&#xff0c;现在我就来给大家讲讲怎么将数据库和java进行联系&#xff0c;我们也可以用数据库代替以前的IO流来存东西了。 JDBC 概述 …

【JDBC-1】jdbc编程六大步骤

文章目录 jdbc编程步骤1.注册驱动2.获取数据库的连接3.获取数据库操作对象4.执行sql语句5.处理查询结果集6.释放资源 jdbc编程步骤 jdbc编程6步&#xff1a;1.注册驱动2.获取连接3.获取数据库操作对象4.执行sql5.处理查询结果集6.释放资源1.注册驱动 注册驱动的作用&#xff…

如何使用jetty

一直都听说jetty跟Tomcat一样&#xff0c;是一个web容器。之前做项目的时候&#xff0c;也使用过jetty&#xff0c;不过当时jetty是作为一个插件&#xff0c;跟maven集成使用的。那个时候&#xff0c;由于是第一次使用jetty&#xff0c;感觉jetty跟Tomcat并不一样&#xff0c;启…

BAT优秀工具

1、腾讯智图&#xff1a;图片压缩优化平台 地址&#xff1a;智图_图片压缩在线工具_在线制作webp 智图可以帮你把图片压缩到很小&#xff0c;但是还是很清晰&#xff01;有很多网站限制图片上传的大小&#xff0c;用这个就可以&#xff01;在线的&#xff0c;很方便&#xff01…

GifCam2.0使用

GifCam2.0使用 有时候我们需要制作一个gif&#xff0c;那么我们可以用GifCam 。 GifCam 是一款能够轻松录制gif的软件&#xff0c;它体积小、免安装。它可以给现有的gif增加帧数。编辑gif图片&#xff0c;如添加文字、删除某些帧、修改特定帧的延迟等。 下载地址&#xff1a;h…

荒野大镖客2-解压即玩

游戏名称&#xff1a;荒野大镖客2发行时间&#xff1a;2019年12月6日游戏详细介绍&#xff1a;https://store.steampowered.com/app/1174180/Red_Dead_Redemption_2/ &#xff08;STEAM网站&#xff09;不了解这是什么游戏的可以看一下版本介绍&#xff1a;该版本由EMPRESS小组…

火炬2 贴吧导航(大纲1.0)

火炬2 贴吧导航&#xff08;大纲1.0&#xff09; time/2020.7.25 前言 因前导航贴中部分链接失效或失时&#xff0c;为方便吧友查阅攻略及数据&#xff0c;故作此帖。 注&#xff1a;此贴仅作导航&#xff0c;不回复不解疑&#xff0c;有个人疑惑请按正常流程询问其他吧友。 …

MYSQL 5.7 分组 1055解决方法

1.免安装版 在根目录下创建 my.ini 将一下内容拷贝到my.ini&#xff0c;然后重启服务即可 [mysqld] sql_mode‘STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION’ 相信有些小伙伴在使用数据库的过程中会经常频繁的启动…

iSystem调试器系列文章—1.连接测试

iSystem调试器系列文章——1.连接测试 做嵌入式开发测试的人一般情况下都听过Lauterbach、iSystem、PLS这三个大名鼎鼎的调试器品牌。可惜的是博主在之前的工作中从未接触过这些大神级的调试器&#xff0c;只闻其声不见其人。今天有幸从厂家获取到了iSystem的IC5700调试器&…

小工具推荐

我们在使用计算机的过程中&#xff0c;总会有各种各样的需求&#xff0c;也许是为了颜值&#xff0c;也许是为了工作效率&#xff0c;有或是为了方便。这些需求系统本身没有提供相应的功能&#xff0c;或者系统提供的功能并不是那么好用。我们就渴望找一个好的工具代替。 本更新…

前端音视频之WebRTC初探

WebRTC Web Real-Time Communication 网页即时通信 WebRTC 于 2011 年 6 月 1 日开源&#xff0c;并在 Google、Mozilla、Opera 等大佬们的支持下被纳入 W3C 推荐标准&#xff0c;它给浏览器和移动应用提供了即时通信的能力。 WebRTC 优势及应用场景 优势 跨平台(Web、Window…

(常规更新)1.C++入门第一讲——编程如此简单

格言&#xff1a;自立才能自主&#xff0c;靠人更须靠己。有志之人立长志&#xff0c;无志之人常立志。千里之行&#xff0c;始于足下&#xff1b;艰难困苦&#xff0c;玉汝于成。少壮不努力&#xff0c;老大徒伤悲。✊✊✊ &#x1f4eb; 如果您觉得文章里有错误的地方&#x…

MySQL8.0的下载及安装 (8.0.15)

MySQL8.0的下载及安装 &#xff08;一&#xff09;1.下载MySQL8.0.15 (两种方式1.csdn下载&#xff08;无积分联系我私发&#xff09;2.官网下载) csdn下载链接&#xff1a;https://download.csdn.net/download/qq_42397270/19886950 1.访问MySQL官网&#xff1a;https://www…

JDK17解压版---安装与配置与查看版本

JDK17解压版下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/#jdk17-windows bin目录 &#xff1a;存放各种工具命令&#xff08;如 javac、java、javap等&#xff09;。 conf 目录&#xff1a;里面存放一些配置文件 。 include目录&#xff1a;存…