最近和同事聊天无意间发现他们的系统也存在脑裂的问题。想想当初在我们的系统中为了解决脑裂花了非常大的功夫,现在和大家一起讨论下脑裂,假死等等这些问题和解决的方法。
在一个大集群中往往会有一个master存在,在长期运行过程中不可避免的会出现宕机等问题导致master不可用,在出现这样的情况以后往往会对系统产生很大的影响,所以一般的分布式集群中的master都采用了高可用的解决方案来避免这样的情况发生。
master-slaver方式,存在一个master节点,平时对外服务,同时有一个slaver节点,监控着master,同时有某种方式来进行数据的同步。如果在master挂掉以后slaver能很快获知并迅速切换成为新的master。在以往master-slaver的监控切换是个很大的难题,但是现在有了Zookeeper的话能比较优雅的解决这一类问题。
master-slaver结构
master-slaver实现起来非常简单,而且在master上面的各种操作效率要较其他HA解决方案要高,早期的时候监控和切换很难控制,但是后来zookeeper出现了,他的watch和分布式锁机制很好的解决了这一类问题。
我们的系统和同事的系统都是这种模式,但是后来都发现由于ZooKeeper使用上的问题存在脑裂的问题。
记得很久以前参加一个大牛的技术交流会他就提到过在集群中假死问题是一个非常让人头痛的问题,假死也是导致脑裂的根源。
根据一个什么样的情况能判断一个节点死亡了down掉了,人可能很容易判断,但是对于在分布式系统中这些是有监控者来判断的,对于监控者来说很难判定其他的节点的状态,唯一可靠点途径的就是心跳,包括ZooKeeper就是使用心跳来判断客户端是否仍然活着的,使用ZooKeeper来做master HA基本都是同样的方式,每个节点都尝试注册一个象征master的临时节点其他没有注册成功的则成为slaver,并且通过watch机制监控着master所创建的临时节点,Zookeeper通过内部心跳机制来确定master的状态,一旦master出现意外Zookeeper能很快获悉并且通知其他的slaver,其他slaver在之后作出相关反应。这样就完成了一个切换。这种模式也是比较通用的模式,基本大部分都是这样实现的,但是这里面有个很严重的问题,如果注意不到会导致短暂的时间内系统出现脑裂,因为心跳出现超时可能是master挂了,但是也可能是master,zookeeper之间网络出现了问题,也同样可能导致。这种情况就是假死,master并未死掉,但是与ZooKeeper之间的网络出现问题导致Zookeeper认为其挂掉了然后通知其他节点进行切换,这样slaver中就有一个成为了master,但是原本的master并未死掉,这时候client也获得master切换的消息,但是仍然会有一些延时,zookeeper需要通讯需要一个一个通知,这时候整个系统就很混乱可能有一部分client已经通知到了连接到新的master上去了,有的client仍然连接在老的master上如果同时有两个client需要对master的同一个数据更新并且刚好这两个client此刻分别连接在新老的master上,就会出现很严重问题。
出现这种情况的主要原因在与Zookeeper集群和Zookeeperclient判断超时并不能做到完全同步(这些还依赖于操作系统调度等,很难保证),也就是说可能一前一后,如果是集群先于client发现那就会出现上面的情况了。同时在发现并切换后通知各个客户端也有先后快慢。出现这种情况的几率很小,需要master与zookeeper集群网络断开但是与其他集群角色之间的网络没有问题,还要满足上面那些条件,但是一旦出现就会引发很严重的后果,数据不一致了。
避免这种情况其实也很简单,在slaver切换的时候不在检查到老的master出现问题后马上切换,而是在休眠一段足够的时间,确保老的master已经获知变更并且做了相关的shutdown清理工作了然后再注册成为master就能避免这类问题了,这个休眠时间一般定义为与zookeeper定义的超时时间就够了,但是这段时间内系统可能是不可用的,但是相对于数据不一致的后果我想还是值得的。
当然最彻底的解决这类问题的方案是将master HA集群做成peer2peer的,屏蔽掉外部Zookeeper的依赖。每个节点都是对等的没有主次,这样就不会存在脑裂的问题,但是这种ha解决方案需要使用两阶段,paxos这类数据一致性保证协议来实现,不可避免的会降低系统数据变更的系统,如果系统中主要是对master的读取操作很少更新就很适合了。