前言

ZooKeeper 是一个高可用的分布式数据管理不系统协调框架。基于对 Paxos 算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得 ZooKeeper 解决很多分布式问题。

本文介绍zk的应用场景。zk并非天生就是为这些应用场景设计的,都是后来众多开发者根据其框架的特性,利用其提供的一系列 API 接口,摸索出来的典型使用方法。

前面已经介绍过分布式锁的应用。此文记录zk其他的应用场景。

一、数据发布与订阅(配置中心)

此应用是利用zk提供的watch机制,生产者将消息发送到zk节点上,zk通知监听者节点数据发生了变化,以此来实现消息的订阅与发布。在配置中心中,一些全局的变量,配置在zk中,当发生变化时,通知监听者,动态更新这些信息。具体的应用场景有如下几个:

  • 应用中用到的一些配置信息放到 ZK 上迚行集中管理。这类场景通常是这样:应用在启劢的时候会主劢来获取一次配置,同时,在节点上注册 一个Watcher,这样一来,以后每次配置有更新的时候,都会实时通知到订阅的客户端,从来达到获取最新配置信息的目的。
  • 分布式日志收集系统。这个系统的核心工作是收集分布在不同机器的日志。收集器通常是按照应用来分配收集任务单元,因此需要在 ZK 上创建一个以应用名作为 path 的节点 P,并将这个应用的所有机器 ip,以子节点的形式注册到节点 P 上,这样一来就能够实现机器变动的时候,能够实时通知到收集器调整任务分配。

用zk做消息订阅与发布,适用于数据量小的场景。数据变化频率快也适合。但不适用于数据量大的场景。

二、负载均衡

也是利用zk的watch机制,具体可看kafka中zk的作用,这里不再详细记录

下面是一个简单的描述,大概概括zk在kafka生产者和消费者中的作用:
生产者负载均衡:
发送消息的时候,生产者在发送消息的时候必须选择一台 broker 上的一个分区来发送消息,因此kafka在运行过程中,会把所有 broker
和对应的分区信息全部注册到 ZK 挃定节点上,默认的策略是一个依次轮询的过程,生产者在通过 ZK 获取分区列表之后,会按照 brokerId 和
partition 的顺序排列组织成一个有序的分区列表,发送的时候按照从头到尾循环往复的方式选择一个分区来发送消息。
消费负载均衡:
在消费过程中,一个消费者会消费一个戒多个分区中的消息,但是一个分区只会由一个消费者来消费。消费策略是:

  1. 每个分区针对同一个 group 只挂载一个消费者。
  2. 如果同一个 group 的消费者数目大于分区数目,则多出来的消费者将不参于消费。
  3. 如果同一个 group 的消费者数目小于分区数目,则有部分消费者需要额外承担消费任务。

在某个消费者故障戒者重启等情况下,其他消费者会感知到这一变化(通过 zookeeper watch 消费者列表),然后重新迚行负载均衡,保证所有的分区都有消费者进行消费。

三、命名服务

命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务地址,进程对象等等——这些我们都可以统称他们为名字(Name)。其中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用 ZK 提供的创建节点的 API,能够很容易创建一个全局唯一的 path,这个 path 就可以作为一个名称。

阿里开源的分布式服务框架 Dubbo 中使用 ZooKeeper 来作为其命名服务,维护全局的服务地址列表。
服务提供者在启动的时候,向 ZK 上的指定节点/dubbo/${serviceName}/providers 目彔下写入自己的 URL 地址,这个操作就完成了服务的发布。

服务消费者启动的时候,订阅/dubbo/${serviceName}/providers 目录下的提供者 URL 地址, 并向/dubbo/${serviceName} /consumers 目录下写入自己的 URL 地址。

四、集群管理与 Master 选举

集群机器监控:这通常用于那种对集群中机器状态,机器在线率有较高要求的场景,能够快速对集群中机器变化作出响应。这样的场景中,往往有一个监控系统,实时检测集群机器是否存活。过去的做法通常是:监控系统通过某种手段(比如 ping)定时检测每个机器,或者每个机器自己定时向监控系统汇报“我还活着”。 这种做法可行,但是存在两个比较明显的问题:

  1. 集群中机器有变劢的时候,牵连修改的东西比较多。
  2. 有一定的延时。

利用 ZooKeeper 有两个特性,就可以实时另一种集群机器存活性监控系统:
a. 客户端在节点 x 上注册一个 Watcher,那么如果 x 的子节点变化了,会通知该客户端。
b. 创建 临时节点,一旦客户端和服务器的会话结束或过期,那么该节点就会消失。

例如,监控系统在 /clusterServers 节点上注册一个 Watcher,以后每动态加机器,那么就往 /clusterServers 下创建一个临时节点:/clusterServers/{hostname}. 这样,监控系统就能够实时知道机器的增减情况,至于后续处理就是监控系统的业务了。

Master 选举则是 zookeeper 中最为经典的应用场景了。
在分布式环境中,相同的业务应用分布在不同的机器上,有些业务逻辑(例如一些耗时的计算,网络 I/O 处理),往往只需要让整个集群中的
某一台机器进行执行,其余机器可以共享这个结果,这样可以大大减少重复劳劢,提高性能,于是这个 master 选举便是这种场景下的碰到的主要
问题。

利用 ZooKeeper 的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很轻易的在分布式环境中迚行集群选取了。
另外,这种场景演化一下,就是动态 Master 选举。这就要用到 临时节点的特性了。
上文中提到,所有客户端创建请求,最终只有一个能够创建成功。在这里稍微变化下,就是允许所有请求都能够创建成功,但是得有个创建顺序,于是所有的请求最终在 ZK 上创建结果的一种可能情况是这样:
/currentMaster/{sessionId}-1 , /currentMaster/{sessionId}-2 ,/currentMaster/{sessionId}-3 …… 每次选取序列号最小的那个机器作为
Master,如果这个机器挂了,由于他创建的节点会马上消失,那么之后最小的那个机器就是 Master 了。

在 Hbase 中,也是使用 ZooKeeper 来实现动态 HMaster 的选举。在 Hbase 实现中,会在 ZK 上存储一些 ROOT 表的地址和 HMaster 的地
址,HRegionServer 也会把自己以临时节点(Ephemeral)的方式注册到 Zookeeper 中,使得 HMaster 可以随时感知到各个 HRegionServer的存活状态,同时,一旦 HMaster 出现问题,会重新选举出一个 HMaster 来运行,从而避免了 HMaster 的单点问题

五、分布式栅栏功能实现

所谓栅栏就是多个线程在都执行完成后,主线程再继续往下执行,在单体项目的多线程场景下,可以通过CyclicBarrier类来实现此功能。但是在分布式场景下,多个系统之间如何实现这种效果呢?

zookeeper使用场景 zookeeper 应用场景_java


例如上图,在app系统中,分别异步调用了四个子系统的接口,需要等待四个子系统都返回数据后,app系统再继续往下执行业务。这种分布式栅栏应该如何实现呢?

利用zk的有序节点可以实现该需求。

zookeeper使用场景 zookeeper 应用场景_客户端_02


利用zk当统一协调者。主线程在zk上创建一个父节点barrier。value的值就是需要调用几个异步请求。

然后被调用请求的系统的接口中,在barrier节点下创建有序子节点。

在主线程中,创建monitor线程,获取barrier的子节点数,如果与barrier的value相同,说明异步调用都执行完了。此时,创建start节点。
同时,主线程中定义一个watcher,监听start节点,一旦创建,说明执行完异步调用了,主线程就可以继续往下执行了。

总结

通过上述zk的实际应用场景可知,就是利用zk提供的一系列特性,实习了不同的业务需求场景,总结下来利用的zk特性就是:
强一致性:
同一个节点下不可能创建两个相同名字的节点。利用此特性可以实现命名服务,分布式独占锁等业务场景。

顺序性:
有序节点是按顺序排列好的,利用此特性实现了分布式非独占锁,分布式排序等业务场景。

watch机制:
订阅发布功能,通知客户端数据发生变化,来实现数据的动态获取功能。此特性常与临时节点断开session后自动删除相配合,来进行集群管理,mater选举等等操作。

对于分布式协调的再理解
本质就是利用zk的watch机制,来做数据订阅发布的一系列变种操作,通知子系统,就是在做协调工作。