本文重点分享Zookeeper脑裂问题的解决方法。ZooKeeper是一种用于协调(同步)分布式进程的服务。它提供了一个简单且高性能的协调内核,用户可以在其上构建更复杂的分布式协调功能。脑裂通常发生在集群环境中,例如ElasticSearch和Zookeeper集群。而这些集群环境都有一个统一的特点,就是都有一个大脑,比如ElasticSearch集群中的Master节点,Zookeeper集群中的Leader节点。1、Zookeeper集群节点为什么要部署为奇数Zookeeper容错是指若干个Zookeeper节点服务器宕机后,剩余节点数必须大于宕机数,即剩余节点服务数必须大于n/2。这样Zookeeper集群就可以继续使用,无论奇数还是偶数都可以选举leader。比如最多5台Zookeeper节点机器出现故障,2台可以继续使用,因为剩下的3台大于5/2。至于为什么最好有奇数个节点?这是为了在最大容错服务器数量的情况下节省资源。例如,当最大容错为2时,对应的Zookeeper服务个数,奇数为5,偶数为6,即在有6个Zookeeper服务的情况下,最多可以down掉2个服务。因此,从节省资源的角度来看,没有必要部署6个(偶数个)Zookeeper服务节点。Zookeeper集群有这样一个特点:只要集群中一半以上的机器正常工作,整个集群对外是可用的。也就是说,如果有2个Zookeeper节点,那么只要有1个Zookeeper节点死掉,Zookeeper服务就不能用了,因为1个不超过一半,所以2个Zookeeper的死亡容忍度为0。同理,如果有3个Zookeeper,其中一个死了,还剩下2个正常的,多了一半,所以3个Zookeeper的容忍度为1。同理,也可以多列几个:2->0;3->1;4->1;5->2;6->2,你会发现一个规律,2n和2n-1的容忍度是一样的,都是n-1,那么为了更高效,何必加那个不必要的Zookeeper。因此,综合以上可以得出结论,从节省资源的角度来说,Zookeeper集群最好部署奇数个节点!2、Zookeeper集群中的“脑裂”场景说明,对于一个集群来说,如果要提高集群的可用性,通常会部署多个机房。例如有一个由6台zkServer组成的集群部署在两个机房:图1一般情况下,这个集群只会有一个Leader,所以如果机房之间的网络断开,两个机房的zkServer可以还是互相交流。如果不考虑多数机制,那么每个机房都会选举一个Leader。图2这相当于原来的簇,分为两个簇,出现两个“大脑”,也就是所谓的“裂脑”现象。对于这种情况,其实可以看出应该是一个统一的集群对外提供服务,但是现在两个集群同时对外提供服务。如果过了一段时间,断开的网络突然又连上了,那么这个时候就会出现问题。两个集群都刚刚对外提供了服务,如何合并数据,如何解决数据冲突等等。刚才解释脑裂场景的时候,有个前提是没有考虑多数机制,所以其实Zookeeper集群是不会轻易出现脑裂问题的,原因就在于多数机制。Zookeeper的过半机制:在leader选举过程中,如果一个zkServer获得超过半数的选票,这个zkServer就可以成为leader。举个简单的例子:如果集群中有5台zkServer,那么half=5/2=2,也就是说至少有3台zkServer在leader选举过程中必须投票给同一个zkServer才能遵守多数机制,选择领导者。那么Zookeeper选举过程中为什么一定要有多数机制验证呢?因为这样一来,不需要等待所有的zkServer都投票给同一个zkServer,就可以选举出一个Leader。这样速度更快,所以称为快速领导者选举算法。为什么在Zookeeper的超过半数机制中是大于,而不是大于等于?这与裂脑问题有关。比如回到上面发生脑裂问题的场景(如上图1所示):当机房中间的网络断开时,1号机房的三台服务器会进行leader选举,但此时多数机制的条件是“节点数>3”,也就是说至少需要4个zkServer才能选举出Leader。因此,对于机房1,它不能选举leader,机房2也不能选举leader。这样的话,当整个集群的机房内网络断开时,整个集群就会没有leader。而如果多数机制的条件是“节点数>=3”,那么机房1和机房2都会选举出一个leader,从而产生脑裂。这可以解释为什么过半机制大于不大于等于,目的是为了防止脑裂。假设我们现在只有5台机器,同样部署在两个机房:图3,此时过半机制的条件是“节点数>2”,即至少有3个服务器需要选举一个领导者。此时机房网络断开,对机房1没有影响,leader还是leader;对于机房2,无法选出leader,此时整个集群只有一个leader。因此可以得出结论,通过多半机制,对于一个Zookeeper集群,要么没有Leader,要么只有一个Leader,这样Zookeeper就可以避免脑裂问题。三、Zookeeper集群“脑裂”问题的处理1、什么是脑裂?简单来说,Split-Brain就是比如说当你的集群中有两个节点的时候,他们都知道这个集群需要选举一个master。然后当他们两个之间的沟通没有问题的时候,就会达成共识,选出其中一个做master。但是如果它们之间的通信出现了问题,两个节点就会觉得现在没有master了,于是各自推举自己为master,这样集群中就会有两个master。对于Zookeeper来说,有一个很重要的问题,就是用什么样的情况来判断一个节点是死了还是宕机了?在分布式系统中,这些是由监视器来判断的,但是监视器也很难判断其他节点的状态。唯一可靠的方式就是心跳,所以Zookeeper也是通过心跳来判断客户端是否还活着。使用ZooKeeper做LeaderHA的方式基本相同:每个节点尝试注册一个象征Leader的临时节点,其他注册失败的成为follower,通过watch机制(此处介绍)监听leader创建的。临时节点:Zookeeper通过内部心跳机制来判断leader的状态。一旦leader出现意外,Zookeeper可以快速获知并通知其他follower,其他flower也会做出相应的反应,从而完成一次切换。这种模式也是比较常见的一种模式,大部分基本上都是这样实现的。但是这里有一个很严重的问题,如果不注意,会在短时间内导致系统出现脑裂。因为心跳超时,有可能是Leader挂了,也有可能是Zookeeper节点之间的网络有问题,导致Leader假死。Leader其实并没有死,而是ZooKeeper的网络出了问题,导致Zookeeper认为自己宕机了,然后通知其他节点进行切换,从而让其中一个follower成为了Leader。但是,原来的Leader并没有死。这个时候client也收到了Leader切换的消息,还是会有一些延迟。Zookeeper通信需要一一通知。此时,整个系统一片混乱。很可能有些客户端已经被通知连接到新的leader,而有些客户端仍然连接到旧的leader。如果两个client需要同时更新Leader的同一份数据,而这两个client此时连接的是新旧Leader,就会出现严重的问题。这里简单总结一下:假死:因为心跳超时(网络原因导致),leader被认为已经死了,但是leader还活着;leader网络再次连接,产生两个leader,有的client连接到旧的leader,有的client连接到新的leader。2、Zookeeper脑裂是什么原因造成的?主要原因是Zookeeper集群和Zookeeper客户端在判断超时的时候不能完全同步。同时,发现和切换后通知每个客户端的速度也有一个顺序。一般这种情况出现的概率很小,Leader节点需要断开与Zookeeper集群网络的连接,但是与其他集群角色的网络没有问题,必须满足以上条件,但是一旦出现,会造成非常严重的后果。数据不一致。3、Zookeeper是如何解决“裂脑”问题的?解决Split-Brain问题,一般有以下几种方法:Quorums(仲裁)法:比如在一个有3个节点的集群中,Quorums=2,也就是说集群可以容忍1个节点的故障。可以选出一个领导者,集群仍然可用。比如一个有4个节点的集群,它的Quorums=3,Quorums必须超过3,也就是说集群的容忍度还是1,如果有两个节点失效,整个集群还是失效的。这是Zookeeper用来防止“裂脑”的默认方法;冗余通信(redundantcommunication)方式:集群中采用多种通信方式,防止一种通信方式失效导致集群中的节点无法通信。fencing(共享资源)方式:比如你能看到共享资源,说明你在集群中。能拿到共享资源锁的leader就是leader。如果您看不到共享资源,则说明您不在集群中。仲裁机制模式;启动磁盘锁定模式。要避免Zookeeper“脑裂”的情况其实很简单。Follower节点切换时,在检查到旧的Leader节点有问题后,并不会立即切换,而是休眠足够长的时间,以确保旧的Leader节点已经被告知变化,并在做相关的shutdown之后清理工作,然后注册为主人可以避免此类问题。这个休眠时间一般定义为Zookeeper定义的超时时间,但是在这段时间内系统可能不可用,但是相对于数据不一致的后果来说还是值得的。1)ZooKeeper默认采用Quorums方式来防止“脑裂”现象,即集群中只有超过半数的节点投票选举Leader。这种方式可以保证Leader的唯一性,要么选举出唯一的Leader,要么选举失败。Quorums在zookeeper中的作用如下:集群中的最小节点数用于选举Leader,保证集群可用;在通知客户端数据已安全保存之前,集群中最少数量的节点已经保存了数据。一旦这些节点保存了数据,就会通知客户端数据已安全保存并可以继续执行其他任务。集群中的其余节点最终也将保存数据。假设一个Leader假死了,剩下的follower选举出一个新的Leader。这个时候,老Leader复活了,依然认为自己是Leader。这时候向其他follower发送写请求也会被拒绝。因为每当有新的Leader产生时,都会产生一个epoch标签(标识Leader当前执政时期)。这个时代是渐进的。如果followers确认新Leader的存在并且知道它的epoch,他们将拒绝epoch小于当前Leader。对纪元的所有请求。有不知道新Leader存在的追随者吗?有可能,但肯定不是大多数,否则无法产生新的领导者。Zookeeper的写法也是遵循quorum机制。因此,不被多数人支持的写作是无效的。就算老领导自认是领导,也依然没有任何作用。除了使用上面默认的Quorums方式避免“脑裂”,Zookeeper还可以采取以下预防措施:2)添加冗余的心跳线,比如双线,尽量减少“脑裂”的几率3)启用磁盘锁。服务方锁定共享磁盘。当“脑裂”发生时,对方将完全“拿不走”共享磁盘资源。但是使用锁盘也有很大的问题。如果占用共享盘的一方不主动“解锁”,对方永远得不到共享盘。现实中,如果服务节点突然死机或死机,是无法执行解锁命令的。备份节点不能接管共享资源和应用服务。于是有人设计了HA中的“智能”锁。即服务方只有在发现心跳线全部断开(无法检测到对端)时才启用磁盘锁。它通常不会被锁定。4)设置仲裁机制。例如设置参考IP(如网关IP)。当心跳线完全断开后,两个节点都会ping参考IP。如果失败,说明断点在本端。如果外部“服务”的本地网络链接断了,即使启动(或继续)应用服务也没用,那就主动放弃竞争,让能ping通参考IP的那端启动服务。为了安全起见,无法ping通参考IP的一方干脆重启自己,彻底释放那些可能还被占用的共享资源。
