这篇文章,我们聊聊 ZooKeeper 的五种经典应用场景 。

我们在项目开发维护过程中会有很多公共变量或资源,需要统一管理,以前可以把它们写在程序公共类或者配置文件中,可是这样以后有变动,程序就需要重新部署,很是不方便,而且分布式、微服务等技术出现,修改维护多个项目管理也变得复杂。
为了解决以上问题,实现一次打包多地部署需求,减少项目管理及安全风险,我们需要将可变变量外移并通过页面统一可视化管理,基于此,很多大厂技术团队都建设了配置中心。
下图是配置中心的通用设计:

ZooKeeper 天然具备作为配置中心的特性,表现在如下两点:
1、配置数据存储到 ZooKeeper 的节点中 ;
2、应用注册改该节点的 watch 监听,如果一旦这个节点数据发生变更,那么所有订阅该节点的客户端都可以获取数据变更通知。

1、发布者作为 Zookeeper 客户端首先在 zk 中创建一个节点,该节点的数据内容即为当前应用服务 A 的配置文件。
2、应用服务 A 在启动时从 zk 的节点上读取数据内容,即获取配置文件内容。
3、读取数据内容后,应用服务 A 会向 zk 的该节点注册一个数据内容变更的 watcher 监听。
4、当发布者更新配置内容并将其更新到 zk 的对应节点数据内容上时,zk 会触发相应的 watcher 事件,并向每一个应用 A 推送此事件通知。
5、应用 A 接收到 watcher 事件后,会触发本地的 watcher 回调方法,该方法将从 zk 中重新拉取节点的数据内容,即获取更新后的配置内容。
大规模服务化之后,服务越来越多,服务消费者在调用服务提供者的服务时,需要在配置文件中维护服务提供者的URL地址,当服务提供者出现故障或者动态扩容时,所有相关的服务消费者都需要更新本地配置的URL地址,维护程本很高。
这时,实现服务的上下线动态感知及服务地址的动态维护就显得非常重要了。

上图是我们熟知的 RPC 框架 Dubbo 的架构 ,服务生产者启动之后,会将服务注册到注册中心 ,服务消费者会通过注册中心订阅相关服务, 服务消费者获取到服务路由信息后,调用服务生产者提供的服务。
zookeeper 提供了一种针对 Znode 的订阅/通知机制,就是当 Znode 节点状态发生变化或者 zookeeper 客户端连接状态发生变化时,会触发事件通知;这个机制在服务注册与发现中,对服务调用者及时感知服务提供者的变化提供了非常好的解决方案。

流程:
/dubbo/com.foo.BarService/providers 目录下写入自己的 URL 地址。/dubbo/com.foo.BarService/providers 目录下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers 目录下写入自己的 URL 地址/dubbo/com.foo.BarService 目录下的所有提供者和消费者 URL 地址。分布式架构中, 为了保证服务的可用性,通常会采用集群模式:其中一个机器宕机后,集群中的其他节点会接替故障节点继续工作。
这种场景中,需要从集群中选择一个节点作为 Master 节点,剩余的其他节点作为备份节点随时待命;当原有的 Master 节点出现故障之后,还需要从备份节点中选择一个节点作为 Master 节点继续服务。
实现 Master选举 有两种方式:
使用 zk 的 master 选举是利用了 zk 中多个客户端对同一节点创建时,只有一个客户端可以成功的特性实现。

具体来说,由三步完成:
1、多个客户端同时发起对同一临时节点 /master-election/master 进行创建的请求,最终只能有一个客户端成功。这个成功的客户端主机就是 Master,其它客户端就是 Slave。
2、 Slave 都向这个临时节点的父节点 /master-election 注册一个子节点列表的 watcher 监听。
3、一旦该 Master 宕机,临时节点就会消失,zk 服务器就会向所有 Slave 发送子节点变更事件,Slave 在接收到事件后会调用相应的回调方法,该回调方法会重新向这个父节点创建相应的临时子节点。谁创建成功,谁就是新的 Master。

在设计 ZooKeeper 实现的 Master 选举机制时,考虑到当无法实现完全的 Active-Active 模式(即所有节点同时处理读写请求)的情况下,可以采用以下策略:
NodeDeleted 事件。这意味着每一个 Slave 都在等待前一位(按创建时间排序)的 Slave 或 Master 的失效信号。NodeDeleted 事件触发),它会检查是否有更早创建的节点仍然存在。如果没有,则该 Slave 自动晋升为新的 Master,承担起协调者的职责。如果有更早的节点存在,则更新监听目标至下一个较早的节点。
核心流程如下:
1、创建持久节点(persistent node)ZooKeeper 中创建一个持久节点。这个节点用来保存序列号的信息。
2、设置数据版本号为 -1 ,告诉 ZooKeeper 不要进行版本号校验,客户端总会调用成功,ZooKeeper 会将该节点的数据版本号自动 + 1 。
3、客户端会得到最新的数据版本号,作为序列号使用。由于 ZooKeeper 的版本号是递增的,所以每次调用都会得到一个新的唯一序列号。
该方案的优点是一次调用即可分配序列号,缺点是受限于数据版本号,只能分配 32 位序列号。
伪代码如下:

分布式锁是控制分布式系统同步访问共享资源的一种方式。ZooKeeper 可以实现分布式锁的功能。

核心流程如下:
NodeDeleted 事件。