zookeeper介绍

来源:互联网 发布:去淘宝网买衣服 编辑:程序博客网 时间:2024/06/12 01:05

ZooKeeper概述

ZooKeeper是什么

ZooKeeper曾是Hadoop的子项目,现已成为Apache独立的顶级项目,它是一个针对大型分布式系统的高度可靠的协调系统,提供了高性能的分布式协调服务(a high-performance coordination service for distributed applications)。

因为整个协同分布式系统群就像一个动物园,所以取名为ZooKeeper,致力于保护动物和访客们的安全。

它要解决什么问题

ZooKeeper主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:

  • 统一命名服务
  • 状态同步服务
  • 集群管理
  • 分布式应用配置项

分布式系统中,最大问题便是分布一致性问题(distributed consensus problem),一个集群中的服务器,以谁为Master,服务器之间的数据采用什么协议同步等等。

Google出了Chubby解决这个问题,但并不开源,所以Yahoo在Apache推出了ZooKeeper,并且优化设计了一个类似两阶段提交的协议(Zookeeper Atomic Broadcast,简称ZAB),而Chubby采用的是Paxos协议。

两阶段提交协议(2 phase commit protocol,简称2PC)

它可以保证数据的强一致性,许多分布式关系型数据管理系统采用此协议来完成分布式事务。协议中,机器(或称为节点)分为两类:协调者(或称Leader),参与者(或称Follower)。

阶段1:请求阶段(或称表决阶段)
协调者将通知事务参与者准备提交或取消事务,然后进入表决过程。在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障)。

阶段2:提交阶段
在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。
ZooKeeper的ZAB与它稍有差别的地方在于,只要过半数的参与者通过,就通知他们commit,没有abort的两段式提交,但本质上仍是2PC。

Zookeeper接入

以Dubbo接入为例,只需在配置文件中引用相应的Zookeeper服务器,并标明需要发布或引用的服务即可:

<!--需要引用的Zookeeper --><dubbo:registry address="zookeeper://10.21.10.17:2181"/><!--需要发布的服务 --><dubbo:service interface="com.yangbw.customer.api.ProviderApi" ref="providerService" validation="true"/><!--需要引用的服务 --><dubbo:reference interface="com.yangbw.user.api.UserApi" id="userApi" timeout="30000"></dubbo:reference>

ZooKeeper的主要术语

znode(节点)

ZooKeeper实际上是维护了一个层次关系的数据结构,非常类似于一个标准的文件系统,比如/zookeeper/status。

每个节点在ZooKeeper中都被称为znode,和文件不一样的是,ZooKeeper中,像/app1(文件系统中叫目录)这样的节点也可以关联数据。

znode具有如下特点:

  • znode有临时节点的概念。
    • 临时节点在创建它的会话活动期间存在。
    • 会话终止的时候,临时节点被删除,所以临时节点不能有子节点。
  • znode可以被监控。
    • 包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端。
  • znode可以保留数据。
    • 但是,它不是设计用来作为通用数据库或者大型对象存储的,而是用来存储协调数据的。
    • 协调数据的形式可能是配置、状态信息、聚合等等。
    • 各种形式的协调数据的一个共同特点是:它们通常比较小,以KB来衡量。
    • ZooKeeper客户端和服务器实现会进行检查,以保证znode数据小于1MB。
  • znode中的数据可以有多个版本。
    • 比如某一个路径下存有多个数据版本,那么查询这个路径下的数据就需要带上版本。

zxid(事务ID)

zxid的全称是ZooKeeper Transaction Id,每次请求对应一个唯一的zxid,实际上它是一个时间戳。

每次znode状态变更,都会产生一个zxid。如果zxid a < zxid b ,则可以保证a一定发生在b之前。

每个znode对象的数据结构内容包括以下内容

  • czxid:创建(create)这个znode的事务的zxid
  • mzxid:最后修改(modify)这个znode的事务的zxid
  • ctime:当前znode的创建时间,注意其表现形式为:The time in milliseconds from epoch
  • mtime:当前znode的最后修改时间
  • version:当前znode的版本号,对znode的每次修改,都会导致znode的版本号加一,所以也可以视为是节点的变更次数。
  • cversion:当前znode所有子节点(children)的变更次数
  • aversion:当前znode的ACL的变更次数
  • ephemeralOwner:如果znode是个临时节点,这个字段记录了这个znode所有者的session id;如果znode不是临时节点,这个字段为0。
  • dataLength:znode对应的数据字段长度
  • numChildren:znode子节点的个数

ZooKeeper Session(会话)

  • 客户端和server间采用长连接。
  • 连接建立后,server产生session ID返还给客户端。
  • 客户端定期发送ping包来检查和保持和server的连接。
  • 一旦session结束或超时,会话中所有的临时节点会被删除。
  • 客户端可根据情况设置合适的session超时时间。

ZooKeeper Watcher(配置推送)

ZooKeeper为解决数据的一致性,使用了Watcher的异步回调接口,将服务端znode的变化以事件的形式通知给客户端(依赖于长连接的会话保持),主要是一种反向推送的机制,让客户端可以做出及时响应。比如及时更新后端的可用集群服务列表。

客户端使用单线程对所有事件按顺序同步回调,注意Watcher是一次性的,每次触发后会被自动删除。如果需要再次侦听事件,必须重新注册Watcher。

ZooKeeper的集群管理

所有的server组成一个ZooKeeper的服务,server和server之间的交互是通过协议进行通信,选取出一个leader,负责向follower广播所有变化消息。所有的follower都和leader通信,follower接受来自leader的所有变化信息,保存在自己的内存。

对于客户端的写请求,follower会转发给leader,而客户端的读请求会在follower端直接服务,无须转发给leader。

集群管理中的几个角色如下:

  • Leader(领导者):领导者负责发起投票,并负责投票决议,更新系统状态。
  • Follower(跟随者):Follower用于接收客户端请求并向客户端返回结果,在选举Leader的过程中参与投票。
  • Observer(观察者):Observer可以接收客户端连接,并将写请求转发给Leader节点。
    • 但Observer不参加投票过程,只同步Leader的状态。
    • Observer的目的是为了扩展系统,提高读取速度。
  • Client(客户端):请求发起方。

ZooKeeper的使用场景

数据的订阅与发布

发布与订阅即所谓的配置管理,顾名思义就是将数据发布到zk节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新

例如在网银集群部署的服务器中,你通过后台修订了一条配置信息的参数,如何能快速的让前端十几台服务器同步刷新?

这里可以用到watcher,在应用服务器启动的时候主动到ZooKeeper获取一次,并且在节点上注册一个Watcher,以后每次配置有更新,实时通知到应用,获取最新配置信息。

分布式命名服务

通过调用ZooKeeper的create node api,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。

假设我们系统间存在相互调用,服务名称遵循JNDI规范(或交易名),统一注册到ZK中,并设定具体对应的URL(或是IP和端口),那它便可以作为系统总线,提供分布式服务调度功能。

任务分发协调

对于一些任务分发系统,子任务启动后,到zk来注册一个临时节点,并且定时将自己的进度进行汇报(将进度写回这个临时节点),这样任务管理者就能够实时知道任务进度。

我们注意到,当ZooKeeper被用来进行分布式通知和协调时,能够大大降低系统之间的耦合。

分布式锁

这个主要得益于ZooKeeper为我们保证了数据的强一致性,即用户只要完全相信每时每刻,zk集群中任意节点(一个zk server)上的相同znode的数据是一定是相同的。

锁服务可以分为两类,一个是保持独占,另一个是控制时序。

  • 保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。
    通常的做法是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建distribute_lock节点,最终成功创建的那个客户端也即拥有了这把锁。
  • 控制时序,就是所有试图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。
    做法和上面基本类似, 只是这里distribute_lock已经预先存在,客户端在它下面创建临时有序节点。父节点distribute_lock维持一份sequence,保证子节点创建的时序性,从而也形成了每个客户端的全局时序。

集群机器监控

这通常用于那种对集群中机器状态,机器在线率有较高要求的场景,能够快速对集群中机器变化作出响应。有两种方法:

  1. 监控客户端在节点x上注册一个Watcher ,那么如果x的子节点变化了,会通知该客户端。
  2. 创建EPHEMERAL类型的节点,一旦客户端和服务器的会话结束或过期,那么该节点就会消失。

ZooKeeper的使用陷阱

任何事情都应该正反两面看,ZooKeeper必然也有它的缺陷,列举一下它不适合做什么(或者说已知的缺陷)

不适合做大数据量的存储

简单来说就是不适合做公用存储。原因很简单,每个数据要同步到所有server才返回,既慢,而且消耗带宽,client还容易阻塞。所以这种应用对zk来说太“重”了。

watch机制的陷阱

watch机制是ZooKeeper为了应用而自己加上的。
这个功能有不少陷阱,最根本的原因就是ZooKeeper的watch事件是单向传递的,并不保证通知一定能到达客户端,因此网络不稳定或者client挂掉都会导致丢失watch事件。

牢记watch事件是一次性的

另一种陷阱是client对于watch是一次性接收的,所以一次watch通知后,下一次watch接收必须等到client发出下一次watch请求。所以在这个处理期间如果有新的watch事件发生,会丢失这些事件。

还是watch

client提交watch请求时,有可能收到connection_loss的异常,很不幸,收到这种异常的时候,client无从得知请求是否成功。
因为这个异常的出现是因为连接断开,而连接是在提交请求时断开还是请求正在处理时断开,无从获知。所以应用层如果特别care一致性问题,就必须带上sessionId重连或者重试。

0 0