初识原理系列之Zookeeper

简介

Apache ZooKeeper是由集群(节点组)使用的一种服务,用于在自身之间协调,并通过稳健的同步技术维护共享数据。ZooKeeper本身是一个分布式应用程序,为写入分布式应用程序提供服务。


ZooKeeper的架构

初识原理系列之Zookeeper


zk的架构看着其实很简单,因为它是自己管理自己的,而不像kafka等应用,需要通过第三方来协调交互。
所以zk需要了解的内容主要有两部分


1.如何协调客户端

2.如何自我管理


如何协调客户端

主要通过以下三个部件

数据模型
Sessions
Watches


数据模型

        下图描述了用于内存表示的ZooKeeper文件系统的树结构。数据结构中每个节点称为 znode 。每个znode由一个名称标识,并用路径(/)序列分隔。

        在图中,首先有一个由“/”分隔的znode每个znode最多可存储1MB的数据。这与UNIX文件系统相类似,除了父znode也可以存储数据。这种结构的主要目的是存储同步数据并描述znode的元数据。此结构称为 ZooKeeper数据模型。

初识原理系列之Zookeeper

        ZooKeeper数据模型中的每个znode都维护着一个 stat 结构。一个stat仅提供一个znode的元数据。它由版本号,操作控制列表(ACL),时间戳和数据长度组成。

版本号 - 每个znode都有版本号,这意味着每当与znode相关联的数据发生变化时,其对应的版本号也会增加。当多个zookeeper客户端尝试在同一znode上执行操作时,版本号的使用就很重要。

操作控制列表(ACL) - ACL基本上是访问znode的认证机制。它管理所有znode读取和写入操作。

时间戳 - 时间戳表示创建和修改znode所经过的时间。它通常以毫秒为单位。ZooKeeper从“事务ID"(zxid)标识znode的每个更改。Zxid 是唯一的,并且为每个事务保留时间,以便你可以轻松地确定从一个请求到另一个请求所经过的时间。

数据长度 - 存储在znode中的数据总量是数据长度。你最多可以存储1MB的数据。

Znode的类型

持久节点  - 即使在创建该特定znode的客户端断开连接后,持久节点仍然存在。默认情况下,除非另有说明,否则所有znode都是持久的。

临时节点 - 客户端活跃时,临时节点就是有效的。当客户端与ZooKeeper集合断开连接时,临时节点会自动删除。因此,只有临时节点不允许有子节点。如果临时节点被删除,则下一个合适的节点将填充其位置。临时节点在leader选举中起着重要作用。

顺序节点 - 顺序节点可以是持久的或临时的。当一个新的znode被创建为一个顺序节点时,ZooKeeper通过将10位的***附加到原始名称来设置znode的路径。例如,如果将具有路径 /myapp znode创建为顺序节点,则ZooKeeper会将路径更改为 /myapp0000000001 ,并将下一个***设置为0000000002。如果两个顺序节点是同时创建的,那么ZooKeeper不会对每个znode使用相同的数字。顺序节点在锁定和同步中起重要作用。


Sessions(会话)

      会话对于ZooKeeper的操作非常重要。会话中的请求按FIFO顺序执行。一旦客户端连接到服务器,将建立会话并向客户端分配会话ID

      客户端以特定的时间间隔发送心跳以保持会话有效。如果ZooKeeper集合在超过服务器开启时指定的期间(会话超时)都没有从客户端接收到心跳,则它会判定客户端死机。

      会话超时通常以毫秒为单位。当会话由于任何原因结束时,在该会话期间创建的临时节点也会被删除。

Watches(监视)

     监视是一种简单的机制,使客户端收到关于ZooKeeper集合中的更改的通知。客户端可以在读取特定znode时设置WatchesWatches会向注册的客户端发送任何znode(客户端注册表)更改的通知。
        Znode更改是与znode相关的数据的修改或znode的子项中的更改。只触发一次watches。如果客户端想要再次通知,则必须通过另一个读取操作来完成。当连接会话过期时,客户端将与服务器断开连接,相关的watches也将被删除。
       一旦ZooKeeper集合启动,它将等待客户端连接。客户端将连接到ZooKeeper集合中的一个节点。它可以是leaderfollower节点。一旦客户端被连接,节点将向特定客户端分配会话ID并向该客户端发送确认。如果客户端没有收到确认,它将尝试连接ZooKeeper集合中的另一个节点。 一旦连接到节点,客户端将以有规律的间隔向节点发送心跳,以确保连接不会丢失。   

      WatcherZookeeper用来实现distribute lock, distribute configure, distribute queue等应用的主要手段。要监控data_tree上的任何节点的变化(节点本身的增加,删除,数据修改,以及孩子的变化)都可以在获取该数据时注册一个Watcher,这有很像Listener模式。一旦该节点数据变化,Follower会发送一个notification responseclient收到notification响应,则会查找对应的Watcher并回调他们
      Client可以在某个ZNode上设置一个Watcher,来WatchZNode上的变化。如果该ZNode上有相应的变化,就会触发这个Watcher,把相应的事件通知给设置WatcherClient。需要注意的是,ZooKeeper中的Watcher是一次性的,即触发一次就会被取消,如果想继续Watch的话,需要客户端重新设置Watcher。


如何协调客户端

zk为客户端主要提供了以下几个服务

配置管理
名字服务
分布式锁
集群管理
分布通知

1. 配置管理

        在分布式应用中,管理好每个集群的配置很关键。比如hadoop各个集群的配置如何保证同步等情况。许多分布式应用为了让自己更专注与应用,于是把管理的事情都交给了zk。
初识原理系列之Zookeeper

具体流程:

      同个应用的多个client都已经连接到zk,并在某路径下建立自己的watcher,当该路径下的配置发生了改变,那么所有client通过watcher就会收到配置变化的讯息,从而请求获取,达到配置同步。


2. 名字服务 

      分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务。类似于域名与ip之间对应关系,域名会更容易记住。
      通过名称来获取资源或服务的地址,提供者等信息。而这个名称在zk中就很容易实现。
通过调用zkcreate node api,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。


3. 分布式锁

        zk在这里的作用其实是如何让client知道自己是否得到了锁,得到了锁的才能进行资源的访问。    

      在需要获取分布式锁的时候,client会在locker节点下创建临时顺序节点,释放锁的时候删除该临时节点。客户端调用createNode方法在locker下创建临时顺序节点,然后调用getChildren(“locker”)来获取locker下面的所有子节点,注意此时不用设置任何Watcher。客户端获取到所有的子节点path之后,如果发现自己所创建的子节点在之前创建的子节点序号最小,那么就认为该客户端获取到了锁。如果发现自己创建的节点并非locker所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时对其注册事件监听器。之后,让这个被关注的节点删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是locker子节点中序号最小的,如皋是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。


4. 集群管理

1. 集群机器监控

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

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

2. Master选举

      在分布式环境中,相同的业务应用分布在不同的机器上,有些业务逻辑(例如一些耗时的计算,网络I/O处理),往往只需要让整个集群中的某一台机器进行执行, 其余机器可以共享这个结果,这样可以大大减少重复劳动,提高性能,于是这个master选举便是这种场景下的碰到的主要问题。
     利用ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客户端请求能够创建成功。


5.分布通知/协调

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

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


再详细说一下zk与客户端交互的流程

初识原理系列之Zookeeper

1.客户端先向zk集群中的一个server发出写请求

2.server告诉leader有客户端发来写请求

3.leader进行响应,并要求它的follower与它同步

4.等待follower的写入完成确认,统计完成数

5.如果统计到已完成同步的follower数量大于总数的一半,则通知server,让它告诉客户端响应成功(所以follower得是奇数)


举例

kafka是运行在zk上的,它需要zk的协调与控制。
在zk的数据结构中,与kafka相关的内容如下

初识原理系列之Zookeeper

如何自我管理

再放一下zk的架构

初识原理系列之Zookeeper

上图架构角色说明


初识原理系列之Zookeeper


所以zk如何进行自我管理就是如何管理server


Leader 选举

        zookeeper的Leader选举机制比较复杂,再补充吧。毕竟zookeeper帮其他应用如kafka等选leader,却没有谁能帮zookeeper。


Servers 同步

       zookeeper都是帮别人同步,却没谁能帮zk同步,只能自己来。 Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分 别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leaderServer具有相同的系统状态。


个人总结

      其实zk也可以看做一个共享文件系统,因为client总要请求传一些元数据到zk上方便同步与管理。那这么说,HDFS不也可以实现同步的功能吗?要清楚的是zk侧重点不在存储,而是在于管理,所谓管理就是client什么时候需要同步,同步什么,如何保证同步等等问题,所以zk的组成还有一个重要的部分就是通知机制,这个机制就通过watcher和sessions实现。
      像kafka,hbase等应用,本身自己也是分布式的,也可以做一个能实现zk功能的子系统为自己所用,但这样整个框架就变得复杂,所以zk应运而生。