zookeeper存在不稳定原因分析
zookeeper存在不稳定的情况,偶尔会出现集群挂掉,服务不可用的情况。近期对zookeeper容易挂掉的原因做了分析,现在将结果分享给大家。
zookeeper是Apache一个开源的分布式协调服务,提供的功能包括:
配置管理;
分布式同步;
分布式锁;
当然,zookeeper还提供其它一些功能,我们一般用的最多的是上面三种,所以其它的我们不再赘述。
下面首先简单介绍一下普通zookeeper集群的结构和选举机制,zookeeper server集群的模型如下(下面原理性介绍,部分引用自:参考1和参考2,其它zk相关原理,也可以参考这篇文章):
从图中可以看出,在一个zookeeper服务模型中,由若干台机器(一般是奇数台)组成一个zookeeper集群,其中一台机器担任leader,用户分布式集群中的机器,均匀的选择zookeeper集群中的集群进行连接和信息同步。
而集群中的机器,则都保持与leader的数据同步,这就需要选举zookeeper集群的leader,选取leader的过程如下:
(下面介绍的是zookeeper默认的FastLeaderElection算法,这个算法适用于leader重新选举或集群挂掉后恢复选举,这个算法与我们后面描述的问题直接相关,对于新集群的选举,有兴趣的可以研究一下zookeeper的 basic paxos 算法):
首先进入数据(快照)恢复阶段,每个zookeeper server读取本地保存的快照数据,数据中有个对应的zxid,zxid越大,表示该server保存的数据越新(也就越应该被选为leader);
读完数据后,每个zookeeper server向集群中广播自己选出的leader的id(有兴趣的可以了解概念“原子广播”),如果是首次选举,则每个server都发送自己的id,也就是选自己为leader。
一个server广播的数据包括4个部分:
自己所选取的leader的id:首次选举的话,这个id就是自己的id;
当前server保存的数据的zxid:越新就越应该被其它server选为leader,保证数据的最新
逻辑时钟:也就是这是第几次选举,越大表示这是越新的选举;
本机状态:LOOKING,FOLLOWING,OBSERVING,LEADING几种;
每个server收到其它server发来的值后,进行判断,选择所保存的数据最新(zxid最大)、逻辑时钟最大,且在选举状态的id作为leader(当然,还有其它条件,逻辑比较复杂,这里不再赘述),并重新广播。来来回回几次之后,系统达成一致,得票多的为leader,leader被选出。
现在leader被选出,但这并不意味着它能坐稳leader的位置,因为接下来,leader要向所有的follower同步自己所保存的数据(多写问题)。如果这个过程出错或超时,则又需要重新选举leader;
另一个问题,从上面的选举过程中,我们可以看出,为了保证选举过程最后能选出leader,就一定不能出现两台机器得票相同的僵局,所以一般的,要求zk集群的server数量一定要是奇数,也就是2n+1台,并且,如果集群出现问题,其中存活的机器必须大于n+1台,否则leader无法获得多数server的支持,系统就自动挂掉。
对zookeeper集群而言,还有个很重要的问题是磁盘io问题,一般,zookeeper集群中,有若干次次写入更新,就会触发一次数据快照保存(在阿里的公共zk机器,有十万次写入,就会触发一次保存)。如果有频繁写入,则这个快照保存会十分频繁,从而造成IO压力很大,所以现在zookeeper一般都是直接使用物理机,独占磁盘。
从上面的探究中,我们概况了下面的点:
1. zk集群要求集群的机器数为奇数(2n+1),并且任何时刻,存活的机器必须大于n+1,否则集群挂掉;
2. zk集群选举后,要进行数据同步,如果同步失败,重新选举。
一般一个zookeeper集群的机器数为3或5台,不会更多,因为更多的话,在数据同步的时候出现多写问题,造成的系统压力会更大。
那么一般造成zookeeper集群挂掉的原因是什么呢?归根到底一句话:要同步的数据太大!多大?500M
zookeeper集群中leader和follower同步数据的极限值是500M,这500M的数据,加载到内存中,大约占用3个G的内存。数据过大,在每次选举之后,需要从server同步到follower,容易造成下面2个问题:
网络传输超时,因为文件过大,传输超过最大超时时间,造成TimeoutException,从而引起重新选举。
如果调大这个超时值,则很可能达到磁盘读写的上限,目前,每像精卫、tbschedule3等,都有大量的zk写入,这些会触发频繁的磁盘写操作,一旦达到io上限值,就会导致超时,进而触发重新选举或直接导致系统崩溃。
所以,上面两个问题,都可能导致zookeeper集群一直在选举和数据同步之间陷入死循环。
上面的问题在阿里是普遍的,下面看两个例子:
去年tbschedule 3有个bug,是ACL list只会增加不会减少,导致存储的ACL数据过大,进而导致选举后数据同步失败,又重新选举的死循环。导致zk服务器一直起不来,进而导致物流宝调度任务不执行,系统挂掉的时间超过1个小时。
事实上,tbschedule 3还存在写入过于频繁的问题,如果写入频繁,就会频繁触发zookeeper保存快照,进而导致IO压力大。
今年近期,汇金平台所在的公共zk集群因为精卫数据过多而挂掉。像精卫这种应用,在zookeeper集群上的节点非常多,存储的数据有200M左右,很容易导致数据量过大。这次zookeeper停止服务的时间长达一个小时。
下面就针对上面的问题分析一下可能的解决方案:
加zookeeper server机器提高性能:注意,加zookeeper server机器提高的只是读性能,但机器越多,多写问题就越严重,系统也越容易挂掉。所以不可行!
冗余一个集群,作为当前集群的备份:冗余出来的集群可以比较小,平时并不服务,只有当主集群挂掉时,再自动切换提供服务。这种集群级别的冗余貌似可行,其实也有问题,导致这种方案可行性也不高,关键问题就在于,需要将数据从主集群的leader实时同步到备份集群,这就存在一个IO问题,如果数据量大,异常存在网络超时和IO压力大问题,如果单独提供一个方案实现从主集群leader到备份集群的高速同步,那就可以直接用于解决主集群之间挂掉的问题了,更不需要备份集群了。另外,冗余物理机作为备份,绝大多数情况下,这个物理机都可能是不提供服务的,所以有资源浪费的问题。
细化zk集群:也就是说,为防止其它业务的影响。如果你的应用强依赖zookeeper,则应该申请机器资源,单独配置zookeeper服务器,防止其他应用的影响。这是目前比较可行的解决方案。
多机房分布:zookeeper存在一个要求,必须有多于n+1台机器存活,否则整个集群挂掉(zookeeper通过这种方式,牺牲了稳定性,但保证了数据一致性)。在目前阿里的双机房策略下,无论两个机房怎么分配,2n+1台机器在两个机房中,都会存在一个机房的机器数大于n+1的问题,如果这个机房挂掉或机房切换,都可能导致整个zk集群挂掉。如果想要保住zk集群稳定性,就必须至少有3个机房,这样一个机房挂掉时,可以保证仍然有n+1台机器存活。这种方案在以后单元化推广开之后还有可能,在目前双机房情况下,无解。
最后,提供给大家一些使用zookeeper时的注意事项:
能不用zookeeper,就不用zookeeper,如果一定要用,尽量不要强依赖zookeeper;
如果你要用到分布式锁,zookeeper是个不错的选择,如果不需要分布式锁,你应该优先考虑不用zookeeper;
采用监听方式,而不是主动查询方式,相信zookeeper的监听推送吧,只要你实现的代码没问题,它还是很稳定的;
不要对zookeeper频繁写入,它只应该存储控制信息和配置信息,也就是说,它更多应该用来做读操作。
不要把zookeeper作为数据存储器。
不要与那些大应用共用一个zookeeper集群,你可能会被它拖挂的。