Hazelcast split brain

31 Oct 2014

前段时间工作上遇到有关Hazelcast Split Brain的问题,对其内部关于网络脑裂部分的代码做了些分析.这里从学习的角度,结合代码分析下Hazelcast脑裂产生的过程以及Hazelcast是如何处理并恢复的1.

什么是脑裂

wikipedia上关于脑裂的定义:

Split-brain is a term in computer jargon, based on an analogy with the medical Split-brain syndrome. It indicates data or availability inconsistencies originating from the maintenance of two separate data sets with overlap in scope, either because of servers in a network design, or a failure condition based on servers not communicating and synchronizing their data to each other.

在Hazelcast里,简单来说,就是原来有一个10个member的集群,由于某些网络原因,导致其中的4个member和其余的6个member失去网络连接,从而原来10个member的这个集群被分割为两个独立的集群:一个4个member的集群和一个6个member的集群,出现这种现象就被称为Split-Brain Syndrome.

脑裂的产生

脑裂的产生往往是因为网络抖动或不稳定等因素造成,意味着脑裂是由于外界因素的变化而产生的.那么在Hazelcast里,出现网络不稳定状况时,脑裂产生的过程是怎样的呢?想知道脑裂怎么产生,就要先知道Hazelcast的集群管理是怎么做的.

那么首先,在Hazelcast的集群里有两种类型的成员:master和普通member.一个集群里只有一个master,由最早加入集群的节点当选,master负责集群的管理.

其次,Hazelcast集群管理方法(比如heartbeart)有哪些呢,我们在ClusterManager.java这个类中可以找到他们:

registerPeriodicProcessable(new Processable() {
    public void process() {
        heartBeater();
    }
}, heartbeatIntervalMillis, heartbeatIntervalMillis);

registerPeriodicProcessable(new Processable() {
    public void process() {
        sendMasterConfirmation();
    }
}, masterConfirmationIntervalMillis, masterConfirmationIntervalMillis);

registerPeriodicProcessable(new Processable() {
    public void process() {
        sendMemberListToOthers();
    }
}, memberListPublishIntervalMillis, memberListPublishIntervalMillis);

registerPeriodicProcessable(splitBrainHandler,
    splitBrainHandler.getFirstRunDelayMillis(), splitBrainHandler.getNextRunDelayMillis());

这些方法都是注册在ClusterManager.java这个类中,并由独立的线程控制执行.

OK,那我们先看看和产生脑裂相关的三个:

就这样,通过这三个方法,保证了master和集群里的member之间双向连通性,而将连接有问题的成员从集群中移走,这也就是Hazelcast脑裂产生的原理了.

我们再用一个简单的例子来说明一下整个过程3:假设一个集群ClusterA,有5个member(M1,M2..M5,加入集群的顺序也是这样),M1为master,由于网络原因,M4,M5和其他member之间的网络断开了,M4和M5之间网络仍然正常,于是,master M1,通过heartBeater发现M4,M5没有响应,将他俩从集群里移除,更新cluster member列表,并通过sendMemberListToOthers通知M2,M3更新cluster member列表,而M4和M5的heartBeater检测到master M1连不上了,便重新选出M2作为新的master,但在下一次heartBeater检查时发现M2也连不上,于是再选出M3作为新的master,但也连不上,最后选出M4作为master,最后,脑裂出现了,原来的clusterA分成了两个clusterA’: M1,M2,M3和clusterA”: M4,M5,M1和M4分别是两个集群的master.

脑裂的恢复

接下来再看看Hazelcast是如何从脑裂的状态下恢复正常的,当然前提是网络已经恢复正常.

这就要看splitBrainHandler这个方法了,它也是在上面注册到ClusterManager.java里的,顾名思义,它就是专门做脑裂处理的:

而它做所的事情就是寻找网络中的是否还有其他的跟自己是同一个group的cluster,如果存在,则进行merge,直到网络中只有一个cluster为止.这里有两个问题:如何发现其他的cluster?如何做merge?

我们继续之前的例子,再看看恢复的过程,在网络恢复正常后,clusterA’和clusterA”各自的master(M1和M4)开始干活啦,这里假设是用tcp的Network config方式连接,那么M1会向在Network config的配置列表里但不在自己cluster里member,也就是M4,M5,询问cluster info,拿到对方的cluster info发现比自己的cluster小,就坐等对方merge.而M4也是一样,会向M1,M2,M3询问cluster info,拿到后发现比自己的cluster大,于是告诉M5做merge,targetAddress是M1的地址,然后各自都restart,加入到clusterA’里,于是,网络中只剩下clusterA’这个集群了,脑裂恢复了.

Tips: 如何方便查看Hazelcast的集群信息
curl 'http://{ip}:{port}/hazelcast/rest/cluster' 

ip,port分别是hazelcast instance的机器ip和hazelcast的端口.

请求会返回类似下面的一个结果:

Members [5] {
    Member [10.20.17.1:5701]
    Member [10.20.17.2:5701]
    Member [10.20.17.4:5701]
    Member [10.20.17.3:5701]
    Member [10.20.17.5:5701]
 }
  1. 这里的分析都是基于Hazelcast 2.6.9的版本. 

  2. Hazelcast为了避免网络里太多heartbeat的消息包,超过5s(不可配)没响应才会发一次ping,所以MAX_NO_HEARTBEAT_MILLIS这个配置不宜太小,否则会对网络太敏感,比如10s,意味着连续检查2次没响应就认为member脱离集群;但也不宜配得太大,否则对网络出问题时的响应太迟钝,比如10min,如果网络真的出了问题,hazelcast需要10分钟的时间才能确定剔除早已不在集群的member.因此,这个配置需根据实际情况谨慎配置,默认值是300s. 

  3. 这里举的是一个简单脑裂情况的例子,有兴趣的同学可以自己想想其他更烧脑的情况,比如M1,M2,M3互通;M4,M5互通;M1可以连到M4,但M4连不通M1;M5可以连通M1,但M1连不通M5.