Zookeeper基本原理

Zookeeper简介

Zookeeper顾明思议动物园管理员,它是拿来管大象(Hadoop),蜜蜂(Hive),小猪(Pig)的管理员,Apache HBase和Apache Solr以及LinkedinSensei等项目中都采用到了Zookeeper。

Zookeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Hadoop和HBase的重要组件,Zookeeper是以Fast Paxos算法为基础,实现同步服务,配置维护和命名服务等分布式应用。

它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护,名字服务,分布式同步,组服务等。

Zookeeper作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储,Zookeeper作用主要是用来维护和监控存储的数据的状态变化,从而达到基于数据的集群管理。

Zookeeper如何工作

Zookeeper是作为分布式应用建立更高层次的同步(Synchronization),配置管理(configuration),群组(Groups)以及名称服务(naming)。在编程上,Zookeeper设计很简单,所使用的数据模型风格很像文件系统的目录树结构,简单的说,有点类似Windows 中的注册表的结构,有名称,有树节点,有KEY/VALUE对的关系,可以看做一个树形结构的数据库,分布在不同的机器上做名称管理。

Zookeeper分为两部分:服务器端和客户端,客户端只连接到整个Zookeeper服务的某个服务器上。客户端使用并维护一个TCP连接,通过这个连接发送请求,接受响应,获取观察的事件以及发送心跳。如果这个TCP连接中断,客户端将尝试连接到另外的Zookeeper服务器。客户端第一次连接到Zookeeper服务时,接受这个连接的Zookeeper服务器会为这个客户端建立一个会话。当这个客户端连接到另外的服务器时,这个会话会被新的服务器重新建立。

启动Zookeeper服务器集群环境后,多个Zookeeper服务器在工作前会选举出一个Leader,在接下来的工作中这个被选举出来的Leader死了,而剩下的Zookeeper服务器会知道这个Leader死掉了,在活着的Zookeeper集群中会继续选出一个Leader,选举出leader的目的是为了可以在分布式的环境中保证数据的一致性。如图所示:

另外,ZooKeeper 支持watch(观察)的概念。客户端可以在每个znode结点上设置一个观察。如果被观察服务端的znode结点有变更,那么watch就会被触发,这个watch所属的客户端将接收到一个通知包被告知结点已经发生变化。若客户端和所连接的ZooKeeper服务器断开连接时,其他客户端也会收到一个通知,也就说一个Zookeeper服务器端可以对于多个客户端,当然也可以多个Zookeeper服务器端可以对于多个客户端,如图所示:

通过试验观察到 Zookeeper的集群环境最好有3台以上的节点,如果只有2台,那么2台当中不管那台机器down掉,将只会剩下一个leader,那么如果有再有客户端连接上来,将无法工作,并且剩下的leader服务器会不断的抛出异常。并且客户端连接时还会抛出这样的异常,说明连接被拒绝,并且等待一个socket连接新的连接,这里socket新的连接指的是zookeeper中的一个Follower。

Zookeeper工作原理

Zookeeper 的核心是广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
每个Server在工作过程中有三种状态:
LOOKING:当前Server不知道leader是谁,正在搜寻。
LEADING:当前Server即为选举出来的leader。
FOLLOWING:leader已经选举出来,当前Server与之同步。

核心是原子广播,Zab协议实现这个机制。

Zab协议包括两种无线重复的阶段:

广播模式(正常工作状态)

所有的写请求都会转发给leader,再由leader将更新广播给所有的Follower。

当半数以上的Follower以及将修改持久化后,leader才会提交更新,客户端才能收到一个更新成功的响应。

这个修改被设计成原子性,要么成功,要么失败。

恢复模式(选举leader,同步状态)

触发机制:服务启动,leader崩溃,leader会失去大多数的Follower。

所有的Zookeeper节点选择出一个leader。

其他的机器成为Follower。

一旦半数以上的Follower已经与新的leader完成同步,则阶段完成。

根据Yahoo的一个报告,这个阶段大约只需200毫秒,所以不会明显影响系统性能。

服务器几种状态

LOOKING:当前服务器不知道leader是谁,正在搜寻。

LEADING: 当前服务器即为leader。

FOLLOWER:leader已经选举出来,当前服务器与之同步。

OBSERVING:Observer的状态。

 

设计目标
简单性

精简的文件系统,提供简单的操作和额外的抽象操作

高可用性

应用程序完全可以依赖与它,帮助系统避免单点故障,构建可靠应用

松耦合

参与协调的各方不必同时存在

一致性

分布式应用的一致性问题

 

Zookeeper的数据结构是一个树形结构,非常类似于一个标准的文件系统。每个子节点项都有唯一的路径标识,如 Server1 节点的标识为 /NameService/Server1。

Znode

Zookeeper数据结构中每个节点称为Znode,每个Znode都有唯一的路径,znode 可以有子节点目录,并且每个 znode可以存储数据。znode是有版本的,每个 znode 中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据。

Znode 基本类型 :

PERSISTENT:持久化znode节点,一旦创建这个znode点存储的数据不会主动消失,除非是客户端主动的delete。

PERSISTENT|SEQUENTIAL:顺序自动编号的znode节点,这种znoe节点会根据当前已近存在的znode节点编号自动加 1,而且不会随session断开而消失。

EPHEMERAL:临时znode节点,Client连接到zk service的时候会建立一个session,之后用这个zk连接实例创建该类型的znode,一旦Client关闭了zk的连接,服务器就会清除session,然后这个session建立的znode节点都会从命名空间消失。总结就是,这个类型的znode的生命周期是和Client建立的连接一样的。

PHEMERAL|SEQUENTIAL:临时自动编号节点,znode节点编号会自动增加,但是会随session消失而消失。

Zookeeper它只负责协调数据,一般Znode上的数据都比较小以Kb为测量单位。Zookeeper的client和server的实现类都会验证znode存储的数据是否小于1M。如果数据比较大时,Server之间进行数据同步会消耗比较长的时间,影响系统性能。

Znode可用于存储数据,具有数据存储能力,默认限制在1MB以内。

数据访问:原子性,读写操作只能完整进行。

对于读操作,要么读操作失败,不会只读到部分数据。

对于写操作,写操作会替换znode的所有数据,不会出现部分写的情况。

每个节点有ACL列表控制访问权限。用户客户端可以对znode执行何种操作。

路径必须是绝对路径,必须从“/”开始。

 

 

Watcher

Zookeeper中的Znode产生某种行为时,如何让客户端得到通知,进行相关操作那?Zookeeper中使用Watcher机制,针对Zookeeper服务的“操作”来设置观察,该服务的其它操作可以触发观察。

Zookeeper中的watcher机制类型:

Exists:在path上执行NodeCreated,NodeDeleted ,NodeDataChanged .

getData Watcher: 在path上执行NodeDataChanged ,NodeDeleted .

getChildrenWatcher:在paht上执行NodeDeleted.或在子path上执行NodeCreated ,NodeDeleted 。

Zookeeper中对于某个节点设置Watcher是一次性的,在Znode上watcher触发后会删除该Watcher,所以如果需要对某个Znode节点进行长期关注,在事件触发后,需要在该Znode上重置Watcher。

基本操作

创建节点:

Stringcreate(String path,byte[] data, List<ACL> acl,CreateMode createMode)

创建一个给定的目录节点 path, 并给它设置数据,CreateMode 标识有四种形式的目录节点

删除节点:

void delete(String path,int version)

删除 path 对应的目录节点,version 为 -1 可以匹配任何版本,也就删除了这个目录节点所有数据

查询节点是否存在:

Stat exists(String path,boolean watch/Watcher watcher)

判断某个 path 是否存在,并设置是否监控这个目录节点

获取节点数据:

byte[] getData(String path,boolean watch, Stat stat)

获取这个 path 对应的目录节点存储的数据,数据的版本等信息可以通过 stat 来指定,同时还可以设置是否监控这个目录节点数据的状态

设置节点数据:

Stat setData(String path,byte[] data, int version)

给 path 设置数据,可以指定这个数据的版本号,如果 version 为 -1 怎可以匹配任何版本

获取节点的子节点:

List<String> getChildren(String path,boolean watch)

获取指定 path 下的所有子目录节点,同样 getChildren方法也有一个重载方法可以设置特定的 watcher 监控子节点的状态

Zookeeper一致性的保证

zxid

Zookeeper Transaction ID;Zookeeper的事务ID,是Zookeeper中的时间戳。

每一个针对znode树的更新都会被赋予一个全家唯一的ID,称为zxid。

Zookeeper要求对所有的更新进行编号,它决定了分布式系统的执行顺序。如果zxid z1小于z2,则z1一定发生在z2之前。

Zxid的实现

64位数字

高32位epoch表示leader,每个新leader都会变epoch。

低32位递增计数。

如何保证数据的一致性

顺序一致性

来自任意客户端的更新都会按发送顺序被提交。例如:一个客户端将znode的z值改为a,然后又改为b,那么所有的客户端只能看到b,而看不到a。

原子性

每个更新要么成功,要么失败。

一个失败的更新,不会有客户端看到这个失败的更新。

单一系统影响

任何一个客户端连接到任何一条服务器,看到的都是系统的系统视图。

持久性

一个更新一旦成功,结果会持久化存在并且不会撤销,也不会输故障节点的影响。

及时性

客户端看到的系统视图是有限的,不会超过阈值。

可以使用sync()同步

Zookeeper是一种高性能,可扩展的服务。Zookeeper的读写速度非常快,并且读的速度要比写的速度更快。另外,在进行读操作的时候,Zookeeper依然能够为旧的数据提供服务。这些都是Zookeeper所提供的一致性保证。具有如下特点:

Zookeeper提供的一致性是弱一致性,首先数据的复制有如下规则:Zookeeper确保对znode树的每一个修改都会被复制到集合中超过半数的机器上。那么就有可能有节点的数据不是最新的而被客户端访问到。并且会有一个时间点,在集群中是不一致的。

也就是Zookeeper只保证最终一致性,但是实时的一致性可以由客户端调用自己来保证,通过调用sync()方法。

顺序一致性

客户端的更新顺序与它们被发送的顺序相一致。

原子性

更新操作要么成功要么失败,没有第三种结果。

 单系统镜像

无论客户端连接到哪一个服务器,客户端将看到相同的 ZooKeeper 视图。

【如果数据不一致,怎么能够保证看到相同的视图? 插入/删除/修改都会对数据结构有影响】

 可靠性

一旦一个更新操作被应用,那么在客户端再次更新它之前,它的值将不会改变。。这个保证将会产生下面两种结果:

1 .如果客户端成功地获得了正确的返回代码,那么说明更新已经成果。如果不能够获得返回代码(由于通信错误、超时等等),那么客户端将不知道更新操作是否生效。

2 .当从故障恢复的时候,任何客户端能够看到的执行成功的更新操作将不会被回滚。

实时性

在特定的一段时间内,客户端看到的系统需要被保证是实时的(在十几秒的时间里)。在此时间段内,任何系统的改变将被客户端看到,或者被客户端侦测到。

【伪实时性,太让人误解了,直白点说就是数据可以在十几秒Sync到各个节点,保证最终一致性. 我第一时间看到这个实时性的时候,我就好奇,OracleRAC花了老鼻子劲才保证了实时性和一致性,Zookeeper是如何轻松做到的,原来是个假的,还说的那么让人误会. 】

给予这些一致性保证,ZooKeeper 更高级功能的设计与实现将会变得非常容易,例如: leader 选举、队列以及可撤销锁等机制的实现。

用分布式系统的CAP原则来分析Zookeeper.

1)C: Zookeeper保证了最终一致性,在十几秒可以Sync到各个节点.

2)A: Zookeeper保证了可用性,数据总是可用的,没有锁.并且有一大半的节点所拥有的数据是最新的,实时的. 如果想保证取得是数据一定是最新的,需要手工调用Sync()

3)P: 有2点需要分析的.

      节点多了会导致写数据延时非常大,因为需要多个节点同步.

          节点多了Leader选举非常耗时, 就会放大网络的问题. 可以通过引入observer节点缓解这个问题.

Zookeeper基本框架

Zookeeper集群主要角色有Leader,Learner(Follower,Observer(当服务器增加到一定程度,由于投票的压力增大从而使得吞吐量降低,所以增加了Observer)),以及client。

Leader:领导者,负责投票的发起和决议,以及更新系统状态。

Follower:接受客户端的请求并返回结果给客户端,并参与投票。

Observer:接受客户端的请求,将写的请求转发给leader,不参与投票。Observer目的是扩展系统,提高读的速度。

Client客户端,向Zookeeper发起请求。

Zookeeper配置介绍

tickTime:基本事件单元,以毫秒为单位。这个时间是作为Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime 时间就会发送一个心跳。

dataDir:存储内存中数据库快照的位置,顾名思义就是Zookeeper 保存数据的目录,默认情况下,Zookeeper将写数据的日志文件也保存在这个目录里。

clientPort:这个端口就是客户端连接Zookeeper 服务器的端口,Zookeeper会监听这个端口,接受客户端的访问请求。

initLimit:这个配置项是用来配置Zookeeper 接受客户端初始化连接时最长能忍受多少个心跳时间间隔数,当已经超过5 个心跳的时间(也就是tickTime)长度后Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是5*2000=10 秒。

syncLimit:这个配置项标识 Leader 与 Follower之间发送消息,请求和应答时间长度,最长不能超过多少个tickTime 的时间长度,总的时间长度就是2*2000=4 秒

server.A= B:C:D : A表示这个是第几号服务器,B是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader。

 Zookeeper的基本应用

分布式锁

在一组进程之间提供互斥机制,任一时刻只有一个进程持有该锁。

Zookeeper是顺序的仲裁者,负责分配顺序号。

指定一个作为锁的znode,记为/locks。

每个希望获得锁的客户端在该节点创建短暂顺序znode,记为/locks/lock-1,/locks/lock-2。。。并且设置一个观察。

持有锁的进程释放后,其它进程观察到信息,顺序号最小者获得锁。

任何时间点,顺序号最小的客户端持有锁。

 

基本思想:

1)首先创建一个作为锁目录(znode),通常用它来描述锁定的实体,称为:/lock_node

2)希望获得锁的客户端在锁目录下创建znode,作为锁/lock_node的子节点,并且节点类型为有序临时节点(EPHEMERAL_SEQUENTIAL);

3)当前客户端调用getChildren(/lock_node)得到锁目录所有子节点,不设置watch,接着获取小于自己的兄弟节点

4)获取小于自己的节点不存在,说明当前客户端顺序号最小,获得锁,结束。

5)若果存在,客户端监视(watch)相对自己次小的有序临时节点状态

6)如果监视的次小节点状态发生变化,则跳转到步骤3,继续后续操作,直到退出锁竞争。

分布式队列

一种是常规的先进先出队列

另一种是要等队列成员聚齐之后的太同意按序执行。

Zookeeper与HBase

HRegionServer把自己以Ephedral方式注册到Zookeeper中,HMaster随时感知各个HRegionServer健康状况。

Zookeeper避免HMaster单点故障。

 tbschedule/codis:配置管理

 Zookeeper能够很容易的实现集群管理的功能,如有多台 Server 组成一个服务集群,那么必须要一个“总管”知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它集群必须知道,从而做出调整重新分配服务策略。同样当增加集群的服务能力时,就会增加一台或多台 Server,同样也必须让“总管”知道。

Zookeeper Leader选举

那么什么是leader选举呢?zookeeper为什么需要leader选举呢?zookeeper的leader选举的过程又是什么样子的?

首先我们来看看什么是leader选举。其实这个很好理解,leader选举就像总统选举一样,每人一票,获得多数票的人就当选为总统了。在zookeeper集群中也是一样,每个节点都会投票,如果某个节点获得超过半数以上的节点的投票,则该节点就是leader节点了。

那么zookeeper集群选举的目的又是什么呢?其实这个要清楚明白的解释还是挺复杂的。我们可以简单点想这个问题:我们有一个zookeeper集群,有好几个节点。每个节点都可以接收请求,处理请求。那么,如果这个时候分别有两个客户端向两个节点发起请求,请求的内容是修改同一个数据。比如客户端c1,请求节点n1,请求是set a = 1; 而客户端c2,请求节点n2,请求内容是set a = 2; 那么最后a是等于1还是等于2呢?这在一个分布式环境里是很难确定的。解决这个问题有很多办法,而zookeeper的办法是,我们选一个总统出来,所有的这类决策都提交给总统一个人决策,那之前的问题不就没有了么。

那我们现在的问题就是怎么来选择这个总统呢?在现实中,选择总统是需要宣讲拉选票的,那么在zookeeper的世界里这又如何处理呢?

在QuorumPeer的startLeaderElection方法里包含leader选举的逻辑。Zookeeper默认提供了4种选举方式,默认是第4种: FastLeaderElection。

当 leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的 Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。

我们先假设我们这是一个崭新的集群,崭新的集群的选举和之前运行过一段时间的选举是有稍许不同的,后面会提及。

节点状态:每个集群中的节点都有一个状态 LOOKING, FOLLOWING, LEADING, OBSERVING。都属于这4种,每个节点启动的时候都是LOOKING状态,如果这个节点参与选举但最后不是leader,则状态是FOLLOWING,如果不参与选举则是OBSERVING,leader的状态是LEADING。

         每个server启动时,投票给自己,并询问其它server投票给谁。

         对于其它server的询问,根据自己的状态回复自己推荐的leader id和zxid。

         收到所有server回复后,计算出zxid最大的,将自己推荐的信息设置为这个server。

         计算过程中得票数最大的server为获胜者。如果获胜者得票过半,该server选为leader。否则,继续执行,直到结束。

状态同步

选完leader以后,Zookeeper就进入状态同步过程。

1.      Leader等待server连接。

2.      Follower连接leader,将最大的zxid发送给leader。

3.      Leader根据Follower的zxid确定同步点。

4.      完成同步后通知Follower已经成为update状态。

5.      Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。

工作流程

Leader主要功能:

恢复数据:维持与Learner的心跳,接受Learner请求并判断Learner的请求消息类型;Learner的消息类型主要有PING消息,REQUREST消息,ACK消息,REVALIDATE消息,根据不同的消息类型,进行不同的处理。PING消息是指Learner的心跳消息;REQUEST消息是Follower发送的提议信息,包括写请求及同步请求。ACK消息是Follower对提议的回复,超过半数的Follower通过,则commit该提议;REVALIDATE消息是用来延长SESSION有效时间。

Follower工作流程

Follower工作流程

Follower基本功能

Follower主要有四个功能:
1. 向Leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);
2 .接收Leader消息并进行处理;
3 .接收Client的请求,如果为写请求,发送给Leader进行投票;
4 .返回Client结果。
Follower的消息循环处理如下几种来自Leader的消息:
1 .PING消息:心跳消息;
2 .PROPOSAL消息:Leader发起的提案,要求Follower投票;
3 .COMMIT消息:服务器端最新一次提案的信息;
4 .UPTODATE消息:表明同步完成;
5 .REVALIDATE消息:根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息;
6 .SYNC消息:返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。
Follower的工作流程简图如下所示:

Observer主要功能同Follower的唯一不同的地方就是observer不会参加leader发起的投票。