ZooKeeper 是什么
ZooKeeper 是一个非常特殊的中间件,为什么这么说呢?一般来说,像中间件类的开源产品,大多遵循“做一件事,并做好它。”这样的 UNIX 哲学,每个软件都专注于一种功能上。而 ZooKeeper 更像是一个“瑞士军刀”,它提供了很多基本的操作,能实现什么样的功能更多取决于使用者如何来使用它。
ZooKeeper 最核心的功能是,它提供了一个分布式的存储系统,数据的组织方式类似于 UNIX 文件系统的树形结构。由于这是一个可以保证一致性的存储系统,所以你可以放心地在你的应用集群中读写 ZooKeeper 的数据,而不用担心数据一致性的问题。
分布式系统中一些需要整个集群所有节点都访问的元数据
,比如集群节点信息、公共配置信息等,特别适合保存在 ZooKeeper 中。
在这个树形的存储结构中,每个节点被称为一个“ZNode”。ZooKeeper 提供了一种特殊的 ZNode 类型:临时节点。这种临时节点有一个特性:如果创建临时节点的客户端与 ZooKeeper 集群失去连接,这个临时节点就会自动消失。在 ZooKeeper 内部,它维护了 ZooKeeper 集群与所有客户端的心跳,通过判断心跳的状态,来确定是否需要删除客户端创建的临时节点。
ZooKeeper 还提供了一种订阅 ZNode 状态变化的通知机制:Watcher,一旦 ZNode 或者它的子节点状态发生了变化,订阅的客户端会立即收到通知。
利用 ZooKeeper 临时节点和 Watcher 机制,我们很容易随时来获取业务集群中每个节点的存活状态,并且可以监控业务集群的节点变化情况,当有节点上下线时,都可以收到来自 ZooKeeper 的通知。
ZooKeeper 集群架构
在 ZooKeeper 集群中,服务器角色分为领导者(leader)和学习者(learner),后者又分为观察者(observer)和跟随者(follower),具体功能如下:
- 领导者:
为客户端提供读写服务
,负责投票的发起和决议。集群中只有领导者才能接受写服务。 - 跟随者:
为客户端提供读服务
,如果是写服务则转发给领导者。跟随者在选举过程中进行投票。 - 观察者:
为客户端提供读服务
,如果是写服务就转发给领导者。观察者不参与领导者的选举投票,也不参与写的过半原则机制。
此外,还有一个独立于 Zookeeper 集群的角色:客户端(client)
是连接 Zookeeper 集群的使用者、请求的发起者。一个 ZooKeeper 集群可能会有多个客户端,客户端能任意连接其中一台 ZooKeeper 节点,并读取数据,但所有的客户端都只能向领导者节点去写数据
在 ZooKeeper 的选举中,如果过半的节点都选一个节点作为领导者,那么这个节点就会是领导者节点。正因如此,在 ZooKeeper 集群中,只要有过半的节点是存活的,那么这个 ZooKeeper 就可以正常提供服务。所以在搭建 ZooKeeper 集群时,通常会使用奇数台
,这样做比较节约机器。
比如安装一个 6 台的 ZooKeeper 集群,如果其中 3 台宕机,就会导致集群不可用。以这样的情况来看,安装 6 台和安装 5 台的效果是一样的,所以安装 5 台比较合适。
Zookeeper 特点
一致性:客户端无论连接到集群中的哪个节点,读到的数据都是一样的。
实时性:ZooKeeper 保证客户端在一定的时间间隔内获得结果,包括成功和失败。但由于网络延迟原因,ZooKeeper 不能保证两台客户端同时得到刚更新的消息。如果都需要最新的消息,就需要调用 sync() 接口。
原子性:领导者在同步数据时会保证事务性,要么都成功,要么都失败。
顺序性:一台服务器上如果消息 a 在消息 b 前发布,那么所有服务器上的消息 a 都是在消息 b 前发布的。
Zookeeper 如何保证数据一致性
在 ZooKeeper 的诸多特点中,个人认为最重要的一点就是Zookeeper 的数据一致性。
ZooKeeper 通过 ZAB
协议来进行 ZooKeeper 集群间的数据同步,保证数据的一致性。
ZAB协议进行数据同步保证数据一致性流程:
ZooKeeper 写数据的机制是客户端把写请求发送给领导者节点,领导者节点再将数据通过 Proposal 请求发送给所有节点(包括自己)。所有节点接收数据后都会写到本地磁盘上,之后发送 ACK 请求给领导者。领导者只要接收到过半节点的请求,就会发送 commit 消息给各个节点。各个节点再把消息放入内存(以保证高性能),该消息就会用户可见了。
在以上同步流程中,如果 ZooKeeper 想保证数据一致性,就需要考虑如下两种情况。
情况一:领导者执行了 commit,还没来得及给跟随者发送 commit 时,领导者宕机了,那该如何保证消息一致性?
情况二:客户端把消息写到领导者节点了,但领导者还没发送 Proposal 消息给其他节点就宕机了。在领导者恢复期间,此消息又该如何处理?
情况一处理机制:ZAB 的崩溃恢复机制
针对情况一,当领导者宕机后,ZooKeeper 会选举出新的领导者节点,新的领导者节点启动后,会到磁盘上检查是否存在没有进行 commit 的消息,如果存在,就继续检查其他跟随者有没有对这条消息进行 commit,如果有过半节点对这条消息进行了 ACK,但没有 commit,那么新的领导者就要完成 commit 的操作。
情况二处理机制:ZAB 恢复中删除数据机制
针对情况二,客户端将消息写到领导者节点了,但领导者还没发送 Proposal 消息就宕机了。这时对于用户来说,这条消息是写失败的。假设一段时间后,领导者节点恢复了,而这时它的角色就变成了跟随者。它在检查自己磁盘时会发现自己有一条消息没有进行 commit,这时,它会检测消息的编号。因为每条消息都有编号,由高 32 位和低 32 位组成,高 32 位是用来体现是否发生过领导者切换的,低 32 位用来展示消息顺序。
当上述跟随者检测消息编号时,它会根据高 32 位知道目前领导者已经切换过了,所以就把当前的消息删除,然后与新领导者同步数据,这样就保证了数据的一致性。
ZK的使用场景
分布式协调
这个其实是 zookeeper 很经典的一个用法,简单来说,就好比,你 A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上对某个节点的值注册个监听器,一旦 B 系统处理完了就修改 zookeeper 那个节点的值,A 系统立马就可以收到通知,完美解决。
分布式锁
举个栗子。对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也尝试去创建那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等着,等第一个机器执行完了自己再执行。
元数据/配置信息管理
zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用 zookeeper 来做一些元数据、配置信息的管理,包括 dubbo 注册中心不也支持 zookeeper 么?
HA高可用性
这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zookeeper 来开发 HA 高可用机制,就是一个重要进程一般会做主备两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程。