ZooKeeper服务通常由奇数个ZooKeeper实例构成,其中一个实例为leader角色,其他为follower角色,它们同时维护了层级目录结构的一个副本,并通过ZAB(ZooKeeper Atomic Broadcast)协议维持副本之间的一致性。ZooKeeper将所有数据保存到内存中,具有吞吐率高、延迟低等优点。ZooKeeper读写数据的路径如下:
- 读路径:任意一个ZooKeeper实例均可为客户端提供读服务。ZooKeeper实例数目越多,读吞吐率越高。
- 写路径:任意一个ZooKeeeper实例均可接受客户端的写请求,但需进一步转发给leader协调完成分布式写。ZooKeeper采用了ZAB协议(可认为是一个简化版的Paxos协议),该协议规定,只要多数ZooKeeper实例写成功,就认为本次写是成功的。这意味着,如果一个集群中存在2N+1个ZooKeeper实例,只要其中N+1个实例写成功,则本次写操作是成功的,从容错性角度看,这种情况下,集群的最大容忍失败实例数目为N。由于ZAB协议要求多数写成功即可返回,因此2N+1和2N+2个节点的集群具备的容错能力是相同的(最大容忍失败实例数均为N),这是建议ZooKeeper部署奇数个实例的最主要原因(多一个节点并没有提高容错能力)。需要注意的是,ZooKeeper实例数目越多,写延迟越高。
当leader出现故障时,ZooKeeper会通过ZAB协议发起新一轮的leader投票选举,保证集群中始终有一个可用的leader。
ZooKeeper中多个实例中的内存数据并不是强一致的,它采用的ZAB协议只能保证,同一时刻至少多数节点中的数据是强一致的。为了让客户端读到最新的数据,需给对应的ZooKeeper实例发送同步指令(可通过调用sync接口实现),强制其与leader同步数据。
在ZooKeeper集群中,随着ZooKeeper实例数目的增多,读吞吐率升高,但写延迟增加。为了解决集群扩展性导致写性能下降的问题,ZooKeeper引入了第三个角色:Observer。Observer并不参与投票过程,除此之外,它的功能与follower类似:它可以接入正常的ZooKeeper集群,接收并处理客户端读请求,或将写请求进一步转发给leader处理。由于Observer自身能够保存一份数据提供读服务,因此可通过增加Observer实例数提高系统的读吞吐率。由于Observer不参与投票过程,因此它出现故障并不会影响ZooKeeper集群的可用性。Observer常见应用场景如下:
- 作为数据中心间的桥梁
由于数据中心之间的确定性通信延迟,将一个ZooKeeper部署到两个数据中心会误报网络故障和网络分区导致ZooKeeper不稳定。然而,如果将整个ZooKeeper集群部署到单独一个集群中,另一个集群只部署Observer,则可轻易地解决网络分区问题。
- 作为消息总线
可将ZooKeeper作为一个可靠的消息总线使用,Observer作为一种天然的可插拔组件能够动态接入ZooKeeper集群,通过内置的发布订阅机制近实时获取新的消息。