ZooKeeper 典型应用

ZooKeeper 典型应用

1. 数据发布与订阅(配置中心)

发布与订阅模型,即所谓的配置中心,顾名思义就是**发布者将数据发布到 ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。**例如全局的配置信息,地址列表等就非常适合使用。

应用在启动的时候会主动来获取一次配置,同时,在节点上注册一个 Watcher,这样一来,以后每次配置有更新的时候,都会实时通知到订阅的客户端,从来达到获取最新配置信息的目的。比如:
​ 分布式搜索服务中,索引的元信息和服务器集群机器的节点状态存放在 ZK的一些指定节点,供各个客户端订阅使用。
注意:适合数据量很小的场景,这样数据更新可能会比较快。

  1. 所有订阅者初次启动时都要去zookeeper上指定的节点获取相关的信息
  2. 获取数据的同时还要设置监听,监听节点数据的变化
  3. 一旦节点数据发生改变,监听就会被触发,所有订阅者(相对于zookeeper而言就是客户端)就会收到事件的通知,从而再去获取最新的数据
  4. 获取完之后需要在设置监听
举例:

例如同一个应用系统需要多台 PC Server 运行,但是它们运行的应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的 PC Server,这样非常麻烦而且容易出错。

像这样的配置信息完全可以交给 Zookeeper 来管理,将配置信息保存在 Zookeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中。

应用中的具体使用
  1. 索引信息和集群中机器节点状态存放在zk的一些指定节点,供各个客户端订阅使用。
  2. 系统日志(经过处理后的)存储,这些日志通常2-3天后被清除。
  3. 应用中用到的一些配置信息集中管理,在应用启动的时候主动来获取一次,并且在节点上注册一个Watcher,以后每次配置有更新,实时通知到应用,获取最新配置信息。
  4. 业务逻辑中需要用到的一些全局变量,比如一些消息中间件的消息队列通常有个offset,这个offset存放在zk上,这样集群中每个发送者都能知道当前的发送进度。
  5. 系统中有些信息需要动态获取,并且还会存在人工手动去修改这个信息。以前通常是暴露出接口,例如JMX接口,有了zk后,只要将这些信息存放到zk节点上即可。

2. 命名服务(Naming Service)

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

3. 分布式锁

分布式锁,这个主要得益于 ZooKeeper 保证了数据的强一致性。锁服务可以分为两类,一个是保持独占,另一个是控制时序。
保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把 zk 上的一个 znode 看作是一把锁,通过 create znode 的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。

控制时序,就是所有试图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。做法和上面基本类似,只是这里 /distribute_lock 已经预先存在,客户端在它下面创建临时有序节点(这个可以通过节点的属性控制:CreateMode.EPHEMERAL_SEQUENTIAL 来指定)。Zk 的父节点(/distribute_lock)维持一份 sequence,保证子节点创建的时序性,从而也形成了每个客户端的全局时序。

注意:

1. 控制时序需要注意避免羊群效应:大量客户端收到同一事件的通知,但是只有很少一部分需要处理这一事件。

如果之前的客户端释放了锁,然后触发了其余所有监听的客户端,这就会对zookeeper服务器造成压力。

解决方案: 优化发送条件,关键在于仅当前一个顺序号的子节点消失时才通知下一个客户端,而不是删除时通知所有子节点。

2. 因连接丢失而导致create 操作失败时不清楚节点是否创建成功

这是一个非幂等的问题,所以不能再次尝试创建。

解决方案: 在znode 的名称中嵌入一个ID,如果客户端出现连接对视的情况,重新连接后对锁节点的所有子节点进行检索,看看时候有子节点的名称包含其ID。

3. zookeeper带有一个Java与西安写的生产级别的所实现,名为writelock。

4. 队列管理

一种是常规的先进先出队列,另一种是要等到队列成员聚齐之后的才统一按序执行。
对于第一种先进先出队列,和分布式锁服务中的控制时序场景基本原理一致,这里不再赘述。
第二种队列其实是在FIFO队列的基础上作了一个增强。通常可以在 /queue 这个znode下预先建立一个/queue/num 节点,并且赋值为n(或者直接给/queue赋值n),表示队列大小,之后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否可以开始执行 了。这种用法的典型场景是,分布式环境中,一个大任务Task A,需要在很多子任务完成(或条件就绪)情况下才能进行。这个时候,凡是其中一个子任务完成(就绪),那么就去 /taskList 下建立自己的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL),当 /taskList 发现自己下面的子节点满足指定个数,就可以进行下一步按序进行处理了。

Zookeeper 可以处理两种类型的队列:

  1. 当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
  2. 队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。

队列管理01

关键代码. 同步队列
`void addQueue() throws KeeperException, InterruptedException{ ``       ``zk.exists(root + "/start",true); ``       ``zk.create(root + "/" + name, new byte[0], Ids.OPEN_ACL_UNSAFE, ``       ``CreateMode.EPHEMERAL_SEQUENTIAL); ``       ``synchronized (mutex) { ``           ``List<``String``> list = zk.getChildren(root, false); ``           ``if (list.size() < size) { ``               ``mutex.wait(); ``           ``} else { ``               ``zk.create(root + "/start", new byte[0], Ids.OPEN_ACL_UNSAFE,``                ``CreateMode.PERSISTENT); ``           ``} ``       ``} ``}`

当队列没满是进入 wait(),然后会一直等待 Watch 的通知,Watch 的代码如下:

`public void process(WatchedEvent event) { ``       ``if(event.getPath().equals(root + "/start") &&``        ``event.getType() == Event.EventType.NodeCreated){ ``           ``System.out.println("得到通知"); ``           ``super.process(event); ``           ``doAction(); ``       ``} ``   ``}`

5. 集群管理

一个集群可以在zookeeper的某个父节点下创建对应的子节点,用于代表集群中的每个节点,然后leader节点在它们创建目录节点的父目录节点上调用 getChildren(String path, boolean watch)方法并设置 watch 为 true,由于设置的位置是父节点,当集群中某个子节点死去,这个目录节点也随之被删除,所以 Children 将会变化,这时getChildren上的 Watch 监听将会被触发,所以leader节点就知道已经有某台 Server 死去了。新增 Server 也是同样的原理。

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

    但是存在两个比较明显的问题:

    1. 集群中机器有变动的时候,牵连修改的东西比较多。
    2. 有一定的延时。 利 用ZooKeeper有两个特性,就可以实时另一种集群机器存活性监控系统:
      • 客户端在节点 x 上注册一个Watcher,那么如果 x 的子节点变化了,会通知该客户端。
      • 创建EPHEMERAL类型的节点,一旦客户端和服务器的会话结束或过期,那么该节点就会消失。例 如,监控系统在 /clusterServers 节点上注册一个Watcher,以后每动态加机器,那么就往 /clusterServers 下创建一个 EPHEMERAL类型的节点:/clusterServers/{hostname}. 这样,监控系统就能够实时知道机器的增减情况,至于后续处理就是监控系统的业务了。
  2. Master选举则是zookeeper中最为经典的使用场景了。在 分布式环境中,相同的业务应用分布在不同的机器上,有些业务逻辑(例如一些耗时的计算,网络I/O处理),往往只需要让整个集群中的某一台机器进行执行, 其余机器可以共享这个结果,这样可以大大减少重复劳动,提高性能,于是这个master选举便是这种场景下的碰到的主要问题。利用ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很轻易的在分布式环境中进行集群选取了。另外,这种场景演化一下,就是动态Master选举。这就要用到 EPHEMERAL_SEQUENTIAL类型节点的特性了。上 文中提到,所有客户端创建请求,最终只有一个能够创建成功。在这里稍微变化下,就是允许所有请求都能够创建成功,但是得有个创建顺序,于是所有的请求最终 在ZK上创建结果的一种可能情况是这样: /currentMaster/{sessionId}-1 , /currentMaster/{sessionId}-2 , /currentMaster/{sessionId}-3 …… 每次选取序列号最小的那个机器作为Master,如果这个机器挂了,由于他创建的节点会马上小时,那么之后最小的那个机器就是Master了。

应用中的具体使用
  1. 在搜索系统中,如果集群中每个机器都生成一份全量索引,不仅耗时,而且不能保证彼此之间索引数据一致。因此让集群中的Master来进行全量索引的生成, 然后同步到集群中其它机器。
  2. 另外,Master选举的容灾措施是,可以随时进行手动指定master,就是说应用在zk在无法获取master信息时,可以通过比如http方式,向 一个地方获取master。

6. 分布通知/协调

ZooKeeper 中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对 ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系统能 够收到通知,并作出相应处理。

应用中的具体使用
  1. 另一种心跳检测机制:检测系统和被检测系统之间并不直接关联起来,而是通过zk上某个节点关联,大大减少系统耦合。
  2. 另一种系统调度模式:某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作,实际上是修改 了ZK上某些节点的状态,而zk就把这些变化通知给他们注册Watcher的客户端,即推送系统,于是,作出相应的推送任务。
  3. 另一种工作汇报模式:一些类似于任务分发系统,子任务启动后,到zk来注册一个临时节点,并且定时将自己的进度进行汇报(将进度写回这个临时节点),这样任务管理者就能够实时知道任务进度。总之,使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章