zookeeper原理与应用

zookeeper(简称zk),顾名思义,为动物园管理员的意思,动物对应服务节点,zk是这些节点的管理者。
在分布式场景中,zk的应用很是普遍。 zk的一致性,是指数据在多个副本之间保持一致的特性
CAP定理告诉咱们,在分布式系统设计中,P(分区容错性)是不可缺乏的,所以只能在A(可用性)与C(一致性)间作取舍。 zookeeper是CPhtml

基本概念:

  • 数据节点(dataNode):zk数据模型中的最小数据单元,数据模型是一棵树,由斜杠(/)分割的路径名惟一标识,数据节点能够存储数据内容及一系列属性信息,同时还能够挂载子节点,构成一个层次化的命名空间。java

  • 会话(Session):指zk客户端与zk服务器之间的会话,在zk中,会话是经过客户端和服务器之间的一个TCP长链接来实现的。经过这个长链接,客户端可以使用心跳检测与服务器保持有效的会话,也能向服务器发送请求并接收响应,还可接收服务器的Watcher事件通知。Session的sessionTimeout,是会话超时时间,若是这段时间内,客户端未与服务器发生任何沟通(心跳或请求),服务器端会清除该session数据,客户端的TCP长链接将不可用,这种状况下,客户端须要从新实例化一个Zookeeper对象。node

  • 事务及ZXID:事务是指可以改变Zookeeper服务器状态的操做,通常包括数据节点的建立与删除、数据节点内容更新和客户端会话建立与失效等操做。对于每一个事务请求,zk都会为其分配一个全局惟一的事务ID,即ZXID,是一个64位的数字,高32位表示该事务发生的集群选举周期(集群每发生一次leader选举,值加1),低32位表示该事务在当前选择周期内的递增次序(leader每处理一个事务请求,值加1,发生一次leader选择,低32位要清0)。 事务日志:全部事务操做都是须要记录到日志文件中的,可经过 dataLogDir配置文件目录,文件是以写入的第一条事务zxid为后缀,方便后续的定位查找。zk会采起“磁盘空间预分配”的策略,来避免磁盘Seek频率,提高zk服务器对事务请求的影响能力。默认设置下,每次事务日志写入操做都会实时刷入磁盘,也能够设置成非实时(写到内存文件流,定时批量写入磁盘),但那样断电时会带来丢失数据的风险。算法

  • 数据快照:数据快照是zk数据存储中另外一个很是核心的运行机制。数据快照用来记录zk服务器上某一时刻的全量内存数据内容,并将其写入到指定的磁盘文件中,可经过dataDir配置文件目录。可配置参数snapCount,设置两次快照之间的事务操做个数,zk节点记录完事务日志时,会统计判断是否须要作数据快照(距离上次快照,事务操做次数等于snapCount/2~snapCount 中的某个值时,会触发快照生成操做,随机值是为了不全部节点同时生成快照,致使集群影响缓慢)。数据库

  • 过半:所谓“过半”是指大于集群机器数量的一半,即大于或等于(n/2+1),此处的“集群机器数量”不包括observer角色节点。leader广播一个事务消息后,当收到半数以上的ack信息时,就认为集群中全部节点都收到了消息,而后leader就不须要再等待剩余节点的ack,直接广播commit消息,提交事务。选举中的投票提议及数据同步时,也是如此,leader不须要等到全部learner节点的反馈,只要收到过半的反馈就可进行下一步操做。apache

Zookeeper的角色及架构

zk集群由多个节点组成,其中有且仅有一个leader,处理全部事务请求;follower及observer统称learner。learner须要同步leader的数据。follower还参与选举及事务决策过程。zk客户端会打散配置文件中的serverAddress 顺序并随机组成新的list,而后循环按序取一个服务器地址进行链接,直到成功。follower及observer会将事务请求转交给leader处理。api

Zookeeper的一致性协议

ZAB(ZooKeeper Atomic Broadcast)是为ZooKeeper设计的一种支持崩溃恢复的原子广播协议。bash

包括崩溃恢复(选主+数据同步)和消息广播(事务操做)。服务器

当leader崩溃或者leader失去大多数的follower 进入恢复模式session

崩溃恢复  

半数经过 – 3台机器 挂一台 2>3/2– 4台机器 挂2台 2!>4/2

(1)设置状态为LOOKING,初始化内部投票Vote (id,zxid) 数据至内存,并将其广播到集群其它节点。节点首次投票都是选举本身做为leader,将自身的服务ID、处理的最近一个事务请求的ZXID(ZXID是从内存数据库里取的,即该节点最近一个完成commit的事务id)及当前状态广播出去。而后进入循环等待及处理其它节点的投票信息的流程中。
(2)循环等待流程中,节点每收到一个外部的Vote信息,都须要将其与本身内存Vote数据进行PK,规则为取ZXID大的,若ZXID相等,则取ID大的那个投票。若外部投票胜选,节点须要将该选票覆盖以前的内存Vote数据,并再次广播出去;同时还要统计是否有过半的赞同者与新的内存投票数据一致,无则继续循环等待新的投票,有则须要判断leader是否在赞同者之中,在则退出循环,选举结束,根据选举结果及各自角色切换状态,leader切换成LEADING、follower切换到FOLLOWING、observer切换到OBSERVING状态

广播(同步)

一旦leader已经和多数的follower进行了状态同步后,他就能够开始广播消息了,即进入广播状态。
这时候当一个server加入zookeeper服务中,它会在恢复模式下启动,发现leader,并和leader进行状态同步。待到同步结束,它也参与消息广播。
Zookeeper服务一直维持在Broadcast状态,直到leader崩溃了或者leader失去了大部分的followers支持

很是棒的zk工做流程介绍

ZooKeeper提供了什么?

  • 文件系统
  • 通知机制

Zookeeper能够用来作什么(应用)

zk经常使用于命名服务、配置管理、集群管理、分布式锁、队列管理

命名服务

名字服务
这个主要是做为分布式命名服务,利用zookeeper的文件系统,经过调用zk的create node api,可以很容易建立一个全局惟一的path,这个path就能够做为一个名称。

命名服务是指经过指定的名字来获取资源或者服务的地址,提供者的信息。利用Zookeeper很容易建立一个全局的路径,而这个路径就能够做为一个名字,它能够指向集群中的集群,提供的服务的地址,远程对象等。简单来讲使用Zookeeper作命名服务就是用路径做为名字,路径上的数据就是其名字指向的实体。
服务提供者在启动的时候,向ZK上的指定节点(如/{serviceName}/providers)目录下写入本身的URL地址,这个操做就完成了服务的发布。
服务消费者启动的时候,订阅/{serviceName}/providers/目录下的提供者URL地址(若是一次性使用直接读取便可),并向/{serviceName}/consumers/目录下写入本身的URL地址(若是服务提供者者须要经过ZK获取服务消费者身份,可选)。
全部向ZK上注册的地址都是临时节点,这样就可以保证服务提供者和消费者可以自动感应资源的变化。
如dubbo作的就是上面这部分操做

nameservice:

  • -m 程序运行的方式,指定是服务提供者provider仍是服务消费者consumer
  • -n 服务名称
  • -s Zookeeper的服务地址IP:PORT
服务提供者:  
  nameservice -m provider -n ServiceDemo -s 172.17.0.36:2181  
服务消费者:
  nameservice -m consumer -n ServiceDemo -s 172.17.0.36:2181
服务监控者:
  nameservice -m monitor -n ServiceDemo -s 172.17.0.36:2181
复制代码

第一条命令启动服务提供者,它提供一个ServiceDemo的服务,首次启动后会建立/NameService/、/NameService/ServiceDemo/、/NameService/ServiceDemo/provider/几个路径(永久节点)。而后在服务提供进程在/NameService/ServiceDemo/provider/下建立临时序列节点。

第二条命令启动一个服务消费进程,它在/NameService/ServiceDemo/consumer/下建立临时序列节点,并watch /NameService/ServiceDemo/provider/下的子节点变化事件,及时更新provider列表。

第三条命令是启动一个服务监控进程,它watch /NameService/ServiceDemo/provider,/NameService/ServiceDemo/consumer/两个路径的子节点变化,及时更新provider列表和comsumer列表。

配置管理

经过zookeeper的监听功能 实现配置管理 common.yml改了,也不须要系统A、B、C重启。

系统A、B、C都使用着这份配置 作法:咱们能够将common.yml这份配置放在ZooKeeper的Znode节点中,系统A、B、C监听着这个Znode节点有无变动,若是变动了,及时响应。

系统A、B、C监听着ZooKeeper的节点,一旦common.yml内容有变化,及时响应

参考 : 基于zookeeper实现统一配置管理

集群管理

利用ZooKeeper的两大特性,就能够实现另外一种集群机器存活性监控的系统。例如,监控系统在/clusterServers节点上注册一个Watcher监听,那么但凡进行动态添加机器的操做,就会在/clusterServers节点下建立一个临时节点:/clusterServer/[Hostname]。这样一来,监控系统就可以实时检测到机器的变更状况,至于后续处理就是监控系统的业务了。

分布式锁

zookeeper实现分布式锁的算法流程,假设锁空间的根节点为/lock:

  1. 客户端链接zookeeper,并在/lock下建立临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。
  2. 客户端获取/lock下的子节点列表,判断本身建立的子节点是否为当前子节点列表中序号最小的子节点,若是是则认为得到锁,不然监听/lock的子节点变动消息,得到子节点变动通知后重复此步骤直至得到锁;
  3. 执行业务代码;
  4. 完成业务流程后,删除对应的子节点释放锁。

这个算法有个极大的优化点:假如当前有1000个节点在等待锁,若是得到锁的客户端释放锁时,这1000个客户端都会被唤醒,这种状况称为“羊群效应”;在这种羊群效应中,zookeeper须要通知1000个客户端,这会阻塞其余的操做,最好的状况应该只唤醒新的最小节点对应的客户端。应该怎么作呢?在设置事件监听时,每一个客户端应该对恰好在它以前的子节点设置事件监听,例如子节点列表为/lock/lock-0000000000、/lock/lock-000000000一、/lock/lock-0000000002,序号为1的客户端监听序号为0的子节点删除消息,序号为2的监听序号为1的子节点删除消息。 因此调整后的分布式锁算法流程以下:

  1. 客户端链接zookeeper,并在/lock下建立临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。
  2. 客户端获取/lock下的子节点列表,判断本身建立的子节点是否为当前子节点列表中序号最小的子节点,若是是则认为得到锁,不然监听恰好在本身以前一位的子节点删除消息,得到子节点变动通知后重复此步骤直至得到锁;
  3. 执行业务代码;
  4. 完成业务流程后,删除对应的子节点释放锁。

咱们这里直接使用curator这个开源项目提供的zookeeper分布式锁实现

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.0.0</version>
</dependency>
复制代码
public static void main(String[] args) throws Exception {
    //建立zookeeper的客户端
    RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
    CuratorFramework client = CuratorFrameworkFactory.newClient("10.21.41.181:2181,10.21.42.47:2181,10.21.49.252:2181", retryPolicy);
    client.start();

    //建立分布式锁, 锁空间的根节点路径为/curator/lock
    InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
    mutex.acquire();
    //得到了锁, 进行业务流程
    System.out.println("Enter mutex");
    //完成业务流程, 释放锁
    mutex.release();
    
    //关闭客户端
    client.close();
}
复制代码

参考 : www.dengshenyu.com/java/%E5%88…

队列管理

Zookeeper能够处理两种类型的队列:

  • 同步队列
    当一个队列的成员都聚齐时,这个队列才可用,不然一直等待全部成员到达。例如一个班去旅游,看是否全部人都到齐了,到齐了就发车。例若有个大任务分解为多个子任务,要全部子任务都完成了才能进入到下一流程。
  • 先进先出队列 按照FIFO方式进行入队和出队
相关文章
相关标签/搜索