浅谈Zookeeper

1.简介

ZooKeeper是一个典型的 分布式数据一致性 的解决方案。分布式应用程序可以基于它实现诸如数据发布/订阅负载均衡命名服务分布式协调/通知集群管理Master选举分布式锁分布式队列等功能。

ZooKeeper可以保证如下分布式一致性特性:

  • 顺序一致性
    从同一个客户端发起的事务请求,最终将会严格按照其发起顺序被应用到ZooKeeper中。

  • 原子性
    要么整个集群所有集群都成功应用了某一个事务,要么都没有应用。

  • 可靠性
    一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来,除非有另一个事务又对其进行了变更。

  • 实时性
    一旦一个事务被成功应用,那么客户端能够立即从服务端上读取到这个事务变更后的最新数据状态。ZooKeeper仅仅保证在一定的时间段内,客户端最终一定能够从服务端上读取到最新的数据状态。

  • 单一视图
    无论客户端连接的是哪个ZooKeeper服务器,其看到的服务端数据模型都是一致的。

2. 集群角色
  • Leader
  • Follower
  • Observer

Leader服务器为客户端提供读和写服务。

Follower和Observer都能提供读服务,不能提供写服务。

两者唯一的区别在于,Observer机器不参与Leader选举过程,也不参与写操作的过半写成功策略,因此Observer可以在不影响写性能的情况下提升集群的读性能

一个ZooKeeper集群同一时刻只会有一个Leader,其他都是Follower或Observer。ZooKeeper默认只有Leader和Follower两种角色,没有Observer角色。

3.会话(Session)

客户端启动时,首先会与服务器建立一个TCP连接,从第一次连接建立开始,客户端会话的生命周期也开始了,通过这个连接,客户端能够通过心跳检测和服务器保持有效的会话,也能够向ZooKeeper服务器发送请求并接受响应,同时还能通过该连接接收来自服务器的Watch事件通知

Session的SessionTimeout值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在SessionTimeout规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效

4.数据节点(ZNode)

这里的ZNode可以理解成既是Unix里的文件,又是Unix里的目录。因为每个ZNode不仅本身可以写数据,还可以有下一级文件或目录。

ZooKeeper中的数据节点是指数据模型中的数据单元,称为ZNode。ZooKeeper将所有数据存储在内存中数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个ZNode,如/hbase/master,其中hbase和master都是ZNode。每个ZNode上都会保存自己的数据内容,同时会保存一系列属性信息

Zookeeper中的节点有两种:

  • 持久节点
  • 临时节点

持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在ZooKeeper上。

临时节点的生命周期跟客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。

ZooKeeper的每个ZNode上都会存储数据,对应于每个ZNode,ZooKeeper都会为其维护一个叫作Stat的数据结构,Stat中记录了这个ZNode的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和aversion(当前ZNode的ACL版本)。

在ZooKeeper中,version属性是用来实现乐观锁机制中的『写入校验』的(保证分布式数据原子性操作)

5.事务操作(如何保证顺序性?)

在ZooKeeper中,能改变ZooKeeper服务器状态的操作称为事务操作。一般包括数据节点创建与删除数据内容更新客户端会话创建与失效等操作。

每一个事务请求,ZooKeeper都会为其分配一个全局唯一的事务ID,用ZXID表示,通常是一个64位的数字。每一个ZXID对应一次更新操作,从这些ZXID中可以间接地识别出ZooKeeper处理这些事务操作请求的全局顺序。

6.Watcher

ZooKeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去。该机制是ZooKeeper实现分布式协调服务的重要特性。

Watcher一旦被调用一次就失效,如果想要一直监听,需要再次注册一个相同的watcher。

7.ACL

ZooKeeper采用ACL(Access Control Lists)策略来进行权限控制。ZooKeeper定义了如下5种权限:

  • CREATE: 创建子节点的权限。
  • READ: 获取节点数据和子节点列表的权限。
  • WRITE:更新节点数据的权限。
  • DELETE: 删除子节点的权限。
  • ADMIN: 设置节点ACL的权限。

ACL不是递归的,拥有父节点的权限,但没有子节点的权限。

8.如何保证分布式数据的原子性?

在ZooKeeper中,version属性用来实现乐观锁机制中的 “写入校验”的,从而保证分布式数据的原子性。

在ZooKeeper服务器的PrepRequestProcessor处理器类中,在处理每一个数据更新(setDataRequest)请求时,会进行如下所示的版本检查:

version = setDataRequest.getVersion();

int currentVersion = nodeRecord.stat.getVersion();

if (version != -1 && version != currentVersion) {
    throw new KeeperException.BadVersionException(path);
}

version = currentVersion + 1;

在进行一次setDataRequest请求处理时,首先进行了版本检查:ZooKeeper会从(setDataRequest) 请求中 获取到当前请求的版本version,同时从数据记录 (nodeRecord)中获取到当前服务器上该数据的最新版本currentVersion。如果version为“-1”,那么说明客户端并不要求使用乐观锁,可以忽略版本比对;如果version不是“-1”,那么就比对version和currentVersion,如果两个版本不匹配,那么将会抛出BadVersionException异常

9.ZAB协议(如何保证数据一致性?)

ZooKeeper并没有完全采用Paxos算法。(fast paxos)

ZAB:原子广播协议,即将服务器数据的状态变更以事务(Proposal)的形式广播到所有的副本进程上去(Follower)

所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为Leader服务器,而剩下的其他服务器则成为Follower服务器。Leader服务器负责将一个客户端事务请求转换成一个事务Proposal(提案)并将该Proposal分发给集群中所有的Follower服务器。之后Leader服务器需要等待所有Follower服务器的反馈,一旦超过半数的Follower服务器进行了正确的反馈后,Leader就会再次向所有的Follower服务器分发Commit消息,要求对刚才的Proposal进行提交

ZAB协议包括两种基本的模式,分别是崩溃恢复消息广播

在整个ZooKeeper集群启动过程中,或是当Leader服务器出现网络中断崩溃退出重启等异常情况时,ZAB协议就会进入恢复模式并选举产生新的Leader服务器。当选举产生了新的Leader服务器,同时集群中有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致

崩溃恢复模式包括两个阶段:Leader选举数据同步

当集群中有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个集群就可以进入消息广播模式了。

10.Leader选举(Fast Paxos算法)
  • 服务器的状态:

    • LOOKING 不确定Leader状态。该状态下的服务器认为当前集群中没有Leader,会发起Leader选举
    • FOLLOWING 表明当前服务器角色是Follower,并且它知道Leader是谁
    • LEADING 表明当前服务器角色是Leader,它会维护与Follower间的心跳
    • OBSERVING 观察者状态。表明当前服务器角色是Observer,与Folower唯一的不同在于不参与选举,也不参与集群写操作时的投票。
  • 选票数据结构

    • logicClock 每个服务器会维护一个自增的整数,名为logicClock,它表示这是该服务器发起的第多少轮投票
    • state 当前服务器的状态
    • self_id 当前服务器的myid
    • self_zxid 当前服务器上所保存的数据的最大zxid
    • vote_id 被推举的服务器的myid
    • vote_zxid 被推举的服务器上所保存的数据的最大zxid

投票流程:

自增选举轮次->初始化选票->发送初始化选票->接收外部投票->判断选举轮次->选票PK->统计选票->更新服务器状态

Zookeeper规定所有有效的投票都必须在同一轮次中。每个服务器在开始新一轮投票时,会先对自己维护的logicClock进行自增操作。

每个服务器在广播自己的选票前,会将自己的投票箱清空。该投票箱记录了所收到的选票。票箱中只会记录每一投票者的最后一票,如投票者更新自己的选票,则其它服务器收到该新选票后会在自己票箱中更新该服务器的选票。

每个服务器最开始都是通过广播把票投给自己

服务器会尝试从其它服务器获取投票,并记入自己的投票箱内。如果无法获取任何外部投票,则会确认自己是否与集群中其它服务器保持着有效连接。如果是,则再次发送自己的投票;如果否,则马上与之建立连接。

收到外部投票后,首先会根据投票信息中所包含的logicClock来进行不同处理。

选票PK是基于(self_id, self_zxid)与(vote_id, vote_zxid)的对比。

如果已经确定有过半服务器认可了自己的投票(可能是更新后的投票),则终止投票。否则继续接收其它服务器的投票。

投票终止后,服务器开始更新自身状态。若过半的票投给了自己,则将自己的服务器状态更新为LEADING,否则将自己的状态更新为FOLLOWING。

具体例子参考:http://www.jasongj.com/zookeeper/fastleaderelection/

11.ZK典型应用场景

ZooKeeper是一个高可用的分布式数据管理与协调框架。基于对ZAB算法的实现,该框架能够很好地保证分布式环境中数据的一致性

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

发布者将数据发布到ZooKeeper节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和动态更新。

ZooKeeper采用的是push-pull相结合的方式。

客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知后,需要主动到服务端获取最新的数据(推拉结合)。

11.2 命名服务

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

ZooKeeper的命名服务即生成全局唯一的ID。

11.3 分布式协调/通知

ZooKeeper中特有Watcher注册异步通知机制,能够很好的实现分布式环境下不同机器,甚至不同系统之间的通知与协调,从而实现对数据变更的实时处理。使用方法通常是不同的客户端都对ZK上同一个ZNode进行注册,监听ZNode的变化(包括ZNode本身内容及子节点的),如果ZNode发生了变化,那么所有订阅的客户端都能够接收到相应的Watcher通知,并做出相应的处理。
ZK的分布式协调/通知,是一种通用的分布式系统机器间的通信方式。

11.4 心跳检测
  • 传统的心跳检测机制:
    • 是否可以相互ping通
    • 通过TCP连接固有的心跳检测机制来实现上层机器的心跳检测
  • 基于ZK的心跳检测机制
    可以让不同的进程都在ZK的一个指定节点下创建临时子节点,不同的进程直接可以根据这个临时子节点来判断对应的进程是否存活。
11.5 工作进度汇报

在ZK上选择一个节点,每个任务客户端都在这个节点下面创建临时子节点。通过判断临时节点是否存在来确定任务机器是否存活。各个任务机器会实时地将自己的任务执行进度写到这个临时节点上去,以便中心系统能够实时地获取到任务的执行进度

11.6 Master选举

比如HDFS中Active NameNode的选举、YARN中Active ResourceManager的选举和HBase中Active HMaster的选举。

ZooKeeper将会保证客户端无法创建一个已经存在的ZNode。如果同时有多个客户端请求创建同一个临时节点,那么最终一定只有一个客户端请求能够创建成功。

成功创建该节点的客户端所在的机器就成为了Master。同时,其他没有成功创建该节点的客户端,都会在该节点上注册一个子节点变更的Watcher,用于监控当前Master机器是否存活,一旦发现当前的Master挂了,那么其他客户端将会重新进行Master选举

11.7分布式锁

分布式锁又分为排他锁共享锁两种。

ZooKeeper上的一个ZNode可以表示一个锁。例如/exclusive_lock/lock节点就可以被定义为一个锁。
把ZooKeeper上的一个ZNode看作是一个锁,获得锁就通过创建ZNode的方式来实现。所有客户端都去/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。ZooKeeper会保证在所有客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获得了锁。同时,所有没有获取到锁的客户端就需要到/exclusive_lock节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。

当前获得锁的客户端机器发生宕机或重启,那么该临时节点就会被删除,释放锁
正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除,释放锁

无论在什么情况下移除了lock节点,ZooKeeper都会通知所有在/exclusive_lock节点上注册了节点变更Watcher监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复『获取锁』过程。

可以多个事务同时获得一个对象的共享锁(同时读),有共享锁就不能再加排他锁(因为排他锁是写锁)。

12.数据的存储

ZK上的数据分为3类:

  • 内存数据
  • 磁盘数据
    • 快照
    • 事务日志
12.1 内存数据:
  • 保存的是ZK 的数据模型:树结构
  • 树中单个节点包含的内容:
    • 节点数据
    • 节点 ACL 信息
    • 节点的路径

DataTree 中 nodes 是 Map,表示所有的 ZK 节点,那其内部key是ZNode 的唯一标识 path
ephemerals 是Map,用于存储临时节点,那其内部key是sessionId

12.2 快照数据
  • 异步:异步线程生成快照文件
  • Fuzzy 快照:
    • 快照文件生成过程中,仍然有新的事务提交,
    • 因此,快照文件不是精确到某一时刻的快照文件,而是模糊的,
    • 这就要求事务操作是幂等的,否则产生不一致。
12.3 事务日志
  • 事务日志频繁 flush 到磁盘,消耗大量磁盘 IO
  • 磁盘空间预分配:事务日志剩余空间 < 4KB 时,将文件大小增加 64 MB
  • 磁盘预分配的目标:减少磁盘 seek 次数
  • 建议:事务日志,采用独立磁盘单独存放
12.4内存数据与磁盘数据间的关系
  • 内存数据,是真正提供服务的数据
  • 磁盘数据,作用:
    • 恢复内存数据,恢复现场
    • 数据同步 :集群内,不同节点间的数据同步(另,内存中的提议缓存队列 proposals)

磁盘数据,为什么同时包含:快照、事务日志?
出于数据粒度的考虑

如果只包含快照,那恢复现场的时候,会有数据丢失,因为生成快照的时间间隔太大,即,快照的粒度太粗了
事务日志,针对每条提交的事务都会 flush 到磁盘,因此粒度很细,恢复现场时,能够恢复到事务粒度上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值