ZooKeeper应用场景及方案介绍

本文主要从应用的角度对ZooKeeper做了浅析,试图阐明ZooKeeper是什么、主要应用场景有哪些、常用场景可以怎么设计,由于篇幅原因暂未写ZooKeeper自身设计及一致性方案的实现等,欢迎大家共同探讨、对有误之处进行指正。

ZooKeeper介绍

ZooKeeper是一个开源的分布式协调服务,由雅虎创建,是Google Chubby的开源实现,设计目标是将复杂且易出错的分布式一致性服务封装成高效可靠的原语集,以一系列简单易用的接口提供给用户使用。ZooKeeper同时是一个典型的分布式数据一致性解决方案,致力于提供一个高性能、高可用并且具有严格写顺序的分布式协调服务,分布式应用可以基于它实现如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能,官方经典概述点这里

为什么选择ZooKeeper?

随着分布式架构的出现,很多分布式应用会遇到数据一致性问题,在解决该问题上ZooKeeper是唯一一个成熟稳定且被大规模应用的解决方案。ZooKeeper无论从稳定性、性能及易用性上来说都达到了一个工业级产品的标准。在工程实践中ZooKeeper(以下简称ZK)已经作为核心组件被广泛应用在很多大型分布式系统中,包括Hadoop、Hbase、Storm、Kafka、Dubbo等。

从应用程序的角度看ZooKeeper

那从分布式应用的角度看ZK能看到一幅怎样的视图?一棵树。这里所说的树是一个共享的、树形结构的名字空间,应用可以通过api在这个命名空间中创建组织结构为树形的数据节点,并且这个空间下的各应用程序看到的视图是一致的。应用可以通过ZK客户端连接到服务端(通常是是机器数量为奇数的集群),同时会建立相应的会话。

上述节点对应于ZK服务器内存中一系列被为ZNode的数据节点,而ZNode之间的层级关系就像文件系统的目录结构一样,但和传统的磁盘文件系统不同的是全量数据都存储在内存中,以此来实现提高服务器吞吐、减少延迟的目的,从这一点来说应用只应该存储控制信息和配置信息到ZNode,而不应该用它来存储大量数据。

《ZooKeeper应用场景及方案介绍》

数据节点ZNode

在ZK中“节点”分为两类,第一类同样是指构成zk集群的机器,称之为机器节点;第二类则是指数据模型中的数据节点ZNode。ZK将所有数据存储于内存中,数据模型是一颗ZNode Tree,由斜杠分隔的的路径就是一个ZNode,如/app1/p_1,每个ZNode上都会保存数据内容,还会保存相应属性信息。

ZNode可以分为持久节点和临时节点两类。持久节点是指一旦该ZNode被创建了,除非主动进行删除操作,这个节点就会一直存在;而临时节点的生命周期会和客户端会话绑定在一起,一旦客户端会话失效其所创建的所有临时节点都会被删除。

ZK还支持客户端创建节点时指定一个特殊的SEQUENTIAL属性,这个节点被创建的时候ZK会自动在其节点名后面追加上一个整形数字,该数字是一个由服务端维护的自增数字,以此实现创建名称自增的顺序节点。

监听器Watcher

Watcher是ZK中很重要的特性,ZK允许用户在指定节点上注册一些Watcher,在该节点相关特定事件(比如节点添加、删除、子节点变更等)发生时Watcher会监听到,ZK服务端会将事件通知到感兴趣的客户端上去,该机制是ZK实现分布式协调服务的重要特性。

通知的时候服务端只会告诉客户端一个简单的事件(通知状态、事件类型、节点路径)而不包含具体的变化信息(如原始数据及变更后的数据),客户端如要具体信息再次主动去重新获取数据;此外,无论是服务端还是客户端,只要Watcher被触发ZK就会将其删除,因此在Watcher的使用上需要反复注册,这样轻量的设计有效减轻了服务端压力,如果Watcher一直有效,节点更新频繁时服务端会不断向客户端发送通知,对网络及服务端性能影响会非常大。

典型应用场景

数据发布/订阅(以Dubbo注册中心为例)

Dubbo是集团开源的分布式服务框架,致力于提供高性能和透明化的远程服务调用解决方案和基于服务框架展开的完整SOA服务治理方案。

其中服务自动发现是最核心的模块之一,该模块提供基于注册中心的目录服务,使服务消费方能够动态的查找服务提供方,让服务地址透明化,同时服务提供方可以平滑的对机器进行扩容和缩容,其注册中心可以基于提供的外部接口来实现各种不同类型的注册中心,例如数据库、ZK和Redis等。接下来看一下基于ZK实现的Dubbo注册中心。

《ZooKeeper应用场景及方案介绍》

/dubbo: 这是Dubbo在ZK上创建的根节点。

/dubbo/com.foo.BarService: 这是服务节点,代表了Dubbo的一个服务。

/dubbo/com.foo.BarService/Providers: 这是服务提供者的根节点,其子节点代表了每个服务的真正提供者。

/dubbo/com.foo.BarService/Consumers: 这是服务消费者的根节点,其子节点代表了没一个服务的真正消费者

Dubbo基于ZK实现注册中心的工作流程:

服务提供者:在初始化启动的时候首先在/dubbo/com.foo.BarService/Providers节点下创建一个子节点,同时写入自己的url地址,代表这个服务的一个提供者。

服务消费者:在启动的时候读取并订阅ZooKeeper上/dubbo/com.foo.BarService/Providersz节点下的所有子节点,并解析所有提供者的url地址类作为该服务的地址列表,开始发起正常调用。同时在Consumers节点下创建一个临时节点,写入自己的url地址,代表自己是BarService的一个消费者

监控中心:监控中心是Dubbo服务治理体系的重要一部分,它需要知道一个服务的所有提供者和订阅者及变化情况。监控中心在启动的时候会通过ZK的/dubbo/com.foo.BarService节点来获取所有提供者和消费者的url地址,并注册Watcher来监听其子节点变化情况。

所有服务提供者在ZK上创建的节点都是临时节点,利用的是临时节点的生命周期和客户端会话绑定的特性,一旦提供者机器挂掉无法对外提供服务时该临时节点就会从ZK上摘除,这样服务消费者和监控中心都能感知到服务提供者的变化。

命名服务

命名服务也是分布式系统中比较常见的一类场景,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象,其中较为常见的是一些分布式服务框架中的服务地址列表,通过使用命名服务客户端应用能够制定名字来获取资源的实体、服务地址和提供者的信息等。

上层应用使用命名服务时可能仅需要一个全局唯一的名字,类似于数据库中的唯一主键,用数据库自增id是可以的,但分库分表的情况下就无法依靠数据库的自增属性来唯一标识一条记录了。另外UUID也是一种广泛应用的ID实现方式,但如果是用UUID对服务进行命名的话就太不直观了,从字面意思根本看不出其表达的含义。下面看下用ZK如何实现全局唯一ID的生成。

之前在ZNode介绍时提过,创建节点时可以设定为SEQUENTIAL顺序节点,创建后API会返回这个节点的完整名字,利用这个特性我们就可以来生成全局唯一ID了。

《ZooKeeper应用场景及方案介绍》

所有客户端根据自己的任务类型,在指定类型的任务下创建一个顺序节点,例如“Job-”节点

节点创建完毕后会返回一个完整的节点名称,如Job-0000000001

客户端拿到这个返回值后拼接上type类型,例如type1-Job-000000001,这样就可以作为一个全局唯一的ID了

在ZK中每个数据节点都能维护一份子节点的顺序序列,当客户端对其创建一个顺序子节点时ZK会自动以后缀的形式在其子节点上添加一个序号,该场景就利用了ZK的这个特性。

Master选举

Master选举是一个在分布式系统中常见的应用场景。分布式系统中Master是一个往往用来协调集群中其他角色、处理写操作及复杂逻辑、数据同步等,通常决定对分布式系统状态变更。针对Master选举的需求,我们通常可以使用依靠关系数据库的主键特性在集群中选举出唯一的Master,但是如果当前的Master挂了slave收不到通知怎么办?显然关系数据库做不到通知这一点。

ZK创建节点时有一个重要的特性,利用ZK的强一致性能够很好的保证在分布式高并发情况下节点的创建一定能够保证全局唯一,即ZK会保证客户端无法重复创建一个已经存在的数据节点。也就是说同时有多个客户端请求创建同一个节点最终一定只有一个客户端能够请求创建成功,利用这个特性就能很容易的在分布式环境中进行Master选举了。

《ZooKeeper应用场景及方案介绍》

进行Master选举时客户端启动后可以向ZK请求创建一个临时节点,例如/master_election/master。在多个客户端创建时只有一个能创建成功,那么这个创建成功的客户端所在的机器就成为了Master。同时其他没有创建成功的客户端都可以在节点/master_election上注册一个子节点变更的Watcher来监控当前Master是否在线,一旦发现Master挂了临时节点会被删除,其它客户端会收到通知,开始重新进行Master选举。

因此,如果只要求静态Maser选举的话,可以选择关系数据库;如果需要动态Master选举实现HA的话,ZK是更好的选择。

分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同系统或同一系统不同机器之间共享了同一资源,那访问这些资源时通常需要一些互斥手段来保证一致性,这种情况下就需要用到分布式锁了。

使用关系型数据库是一种简单、广泛的实现方案,但大多数大型分布式系统中数据库已经是性能瓶颈了,如果再给数据库添加额外的锁会更加不堪重负;另外,使用数据库做分布式锁,当抢到锁的机器挂掉的话如何释放锁也是个头疼的问题。

接下来看下使用ZK如何实现排他锁。排他锁的核心是如何保证当前有且只有一个事务获得锁,并且锁被释放后所有等待获取锁的事务能够被通知到。

《ZooKeeper应用场景及方案介绍》

和Master选举类似,在需要获取排他锁时,所有客户端都会试图在/exclusive_lock下创建临时子节点/exclusive_lock/lock,最终只有一个客户端能创建成功,该客户端就获取到了锁。同时没有获取到锁的客户端需要到/exclusive_lock节点上注册一个子节点变更的Watcher监听,用于实时监听lock节点的变更情况。

/exclusive_lock/lock是一个临时节点,在一下两种情况下都有可能释放锁:

当获取锁的客户端挂掉,ZK上的该节点会被删除

正常执行完业务逻辑之后客户端会主动将自己创建的临时节点删除。

无论在什么情况下删除了lock临时节点ZK都会通知在/exclusive_lock节点上注册了子节点变更Watcher监听的客户端,重新发起锁的获取。

分布式屏障

分布式屏障,举个栗子,在大规模分布式并行计算的场景下,最终的合并计算需要基于很多并行计算的子结果来进行,即系统需要满足特定的条件,一个队列的元素必须都聚齐之后才能进行后续处理,否则一直等待。看下如何用ZK来支持这种场景。

《ZooKeeper应用场景及方案介绍》

开始时/queue_barrier是一个存在的节点,数据内容赋值为一个数字n来代表满足条件的机器总数,例如n=10表示只有当/queue_barrier节点下的子节点数量达到10后才会打开屏障继续处理。然后所有的客户端都会到/queue_barrier节点下创建一个临时节点,如/queue_barrier/192.168.0.1。创建完节点之后根据以下步骤来确定执行顺序

调用获取节点数据的api获取/queue_barrier节点的内容:10

调用获取子节点总数的api获取/queue_barrier下的所有子节点,并且注册对子节点变更的Watcher监听

统计子节点个数

如果子节点个数小于10则继续等待,否则打开屏障继续处理

接收到Watcher通知后,重复步骤2

其它常用场景还包括集群管理及分布式队列等,相信大家都已经有方案了,这里就不再叙述。

ZK在其它大型分布式系统中的应用简介

在Hadoop中ZooKeeper主要用于实现HA做主备切换(类似上面讲的Master选举),同时在YARN中又特别提供了ZK来存储应用的运行状态。

Kafaka是由LinkedIn开源的分布式消息系统,是一个吞吐量极高的分布式消息系统,主要用于实现低延迟的发送和收集大量的事件和日志等活跃数据。Kafaka使用ZK作为其分布式协调框架,将消息生产、消息存储和消息消费的过程结合起来,保持包括生产者消费者和Broker在内的所有组件无状态的情况下,建立起生产者和消费者之间的订阅关系,并实现生产者和消费者之间的负载均衡。

HBase全称Hadoop DataBase,是一个基于Hadoop文件系统设计、面向海量数据的高可靠性、高性能、面向列、可伸缩的分布式存储系统。在HBase向在线分布式存储方向发展过程中,开发者发现如果有RegionServer服务器挂掉时系统和客户端都无法及时得知信息,服务难以快速迁移到其它RegionServer服务器上,问题原因是缺少相应的分布式协调组件,于是后来ZooKeeper被加入到HBase的技术体系中。

目前ZooKeeper已经成为HBase的核心组件,应用场景包括系统容错、RootRegion管理、Region状态管理、分布式SplitLog任务管理和Replication管理,除此之外还包括HMaster选举、Table的enable/disable状态记录及几乎所有元数据的存储等。Hbase中所有对ZK的操作都封装在了org.apache.hadoop.hbase.zookeeper这个包中,感兴趣可自行研究。

    原文作者:菜鸟小玄
    原文地址: https://www.jianshu.com/p/2e970fe35c3f
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞