数据订阅发布
场景需求
- 服务器需要即使获取更新的数据
- 集群中有多台服务器,一旦数据更新,所有的服务器都要第一时间获取更新
- 为了减少网络IO,这些服务器不能经常查询数据状态
ZK实现
- ZK建立特殊的目录存放数据
- 需要数据的机器,订阅对应的目录,如果数据发生变换,则ZK主动通知订阅了的机器
- 机器收到ZK的通知后,读取对应目录的数据
说明:ZK存储的这些数据,数据量一般不会太大(最多可能一般几十或者上百M)
服务注册与发现
场景需求
- 不同的功能对应不同类型的服务器组
- 一个服务器组需要其他组的功能
- 一个组A内可能随时上线或者下线服务器
- 利用A组的其他服务器组,需要动态获取A中服务器的状态,以调整自己的请求
ZK实现
- 一个组的服务器在启动时,把自己的IP和Port注册到ZK上对应组的目录下,注意是临时节点
- 启动后,watch自己需要服务的ZK目录,读取对应数据
- 服务目录更新后,ZK会通知watch对应目录的机器,此时收到通知的服务器重新获取目录数据,完成更新
Master选举
场景需求
- 一个服务器组中,需要一个节点作为Master节点,提供特殊的服务
- 服务器组其他的机器需要知道Master节点的状态,一旦Master出现问题,则立刻进行新的Master选举
ZK实现
- 所有的服务器关注watch特定的目是- 所有的服务器关注watch特定的目录
- 初始化时,所有的服务器同时向该目录注册同名的临时节点,比如图中的binding
- 因为同一个ZK的目录下,不允许有重名的孩子节点,所以注册成功的作为Master,其余的虽然注册失败,但是仍然watch该目录
- 如果目录发生更新,则说明Master节点消失了,可能是出了问题或者其他的情况,此时其他服务器执行1,如此循环
集群监控
场景需求
运维时,需要服务器集群各个机器的信息,而且要动态获取,比如这些机器是否正常工作,工作的负载如何等。
ZK实现
参考服务注册发现的方式。注意,如果集群的机器特别多,那么可能需要分组注册,或者其他的方式,否则容易造成,一个机器的更新,导致监控者频繁的获取数据,造成过大的网路IO
分布式锁
场景需求
- 分布式的情况下,改动共享资源时,需要锁机制
- 严格的互斥需要排它锁
- 读写的限制,需要共享锁(读写锁)
ZK实现
排它锁
- 设置ZK锁目录
- 读写时,先创建锁节点(临时),如果失败,说明有其他机器用,此时watch该目;如果成功则进行读写,读写完删除锁节点,并取消watch
- 失败情况,会一直等ZK通知变更,此时再重新创建锁节点。一直重复,直到成功为止。
排它锁也会有“惊群”现象,解决方案参考共享锁的方式即可。改进后,成了一个类似于FIFO的任务队列的方式
共享锁
简单情景的共享锁
- 获取所有的ZK目录下的锁节点,并watch锁目录
- 创建自己临时锁节点,确定自己的节点序号在所有节点中的位置
- 对于读请求,如果目录为空或者有比自己序号小的写请求,则等待;否则读完删除锁,并取消watch
- 对于写请求,只要有比自己序号小的请求,则等待;否则写完删除锁,并取消watch
- 不成功的情况,等待ZK通知,并重复执行过程
这种方式简单,但是如果机器很多,则一个锁取消后,会有其他很多的等待的机器都收到通知,出现“惊群”现象。
避免“惊群”的共享锁
上述情况,我们发现,如果读失败了,则只需要关注比自己序号小的操作中,最大的写锁;而写操作只需要关注比自己序号小的操作中,最大的锁即可。这样在客户端中更改对应的逻辑即可,但是增加了客户端的复杂性。
分布式队列
场景需求
- FIFO的任务执行模型,只有前一个操作完成后,后面的操作才可以执行
- BARRIER模型,当所有的任务都完成后,才可以执行下一个任务。
ZK实现
FIFO任务队列
假设如下方式建模,002 watch 001,003 watch 002 以此类推,收到变更通知后,再执行自己的任务。注意都是临时节点
BARRIER任务队列
自己注册临时节点到目录上,并watch父节点,只要收到通知,则获取一次孩子节点的个数,等个数满足10个后,才执行任务