Zookeeper总结

原创
2018/08/15 16:34
阅读数 49

 

 

Zookeeper的部分概念

  1. 什么是zookeeeper?

Zookeeper是一个分布式服务的协调中心

 

  1. zookeeper节点的角色类型?

Leader(领导者)、Follower(跟随者)、Observer(观察者)

Leader 负责更新系统的状态,leader投票的发起和决议

Follower 用于接收客户端请求并向客户端返回处理请求的结果,在选举过程中参与投票

Observer可以接收客户端的连接,将写请求转发给Leader,但Observer不参加投票过程,只同步Leader的状态。Observer的目的是为了扩展系统,提高读取速度。

 

  1. zookeeper的节点的状态?

zookeeper节点有四种状态

Looking:寻找Leader状态,当Server处于该状态时,此Server会认为当前集群中没有Leader,需要进入Leader选举状态。

   Following 跟随者状态,表明该Server角色为Follower

   Leading 领导者状态,表明当前服务器角色是Leader

   Observing 观察者状态,表明当前服务器角色是Observer

 

  1. zookeeper的watcher机制?

ZooKeeper 的 Watcher 机制,可以分为三个过程:

客户端注册 Watcher、

服务器处理 Watcher 、

客户端回调 Watcher

 

客户端注册:

在创建一个 ZooKeeper 客户端对象实例时,可以向构造方法中传入一个默认的 Watcher

ZooKeeper 客户端也可以通过 getData、exists 和 getChildren 三个接口来向 ZooKeeper 服务器注册 Watcher,watcher的触发(  create、delete、setData )

服务端处理:

Server在接收Client请求时,会检测此次request体中是否持有watcher信息,如果有,则会导致Server端的watcher列表中新增一个此path关联的watch,只有exist/getChildren/getData会导致此操作.记住watcher信息将会被保存在ZKDatabase中(内存中,而非持久,ZKDatabase会持久Session/ACL/Data).

对于create/setData/delete请求,将会触发watcher列表的检测,比如create操作,创建一个path,在实际的数据存储结束后,将会在watch列表中遍历是否有此path所关联的watches,如果有,则依次触发.

客户端的回调:

Client接收到Event响应结果之后,将会把此消息体放在eventQueue中,等待EventThread去remove并触发. EventThread将event队列中的事件,逐个移除并处理,每移除一个event,都会导致Client本地维护的watcher列表删除相应的watcher(根据path和event类型决定),移除之后并获取到Client维护的watcher对象(此对象就是先前的操作中注册的watcher),watcher对象明确了回调方法,此时将会执行watcher.process(),那么调用者的业务方法将会在此刻被执行.[对于业务方法被执行,从整个周期中,我们可以认为是异步的].

对于节点的create操作,将会触发先前注册的"exist""getChildren"事件被触发;对于节点的delete操作,将会触发先前注册的"exsit""getChildren"事件被触发;对于节点的setData操作,将会触发先前注册的"getData"事件被触发......每个触发的事件都会包含事件的类型(比如:nodeCreate,nodeDelete等),对于用户自定义的watch.process()方法中可以根据事件类型做特定的处理.

 

 

  1. zookeeper的watcher的特点?

一次性

无论是服务端还是客户端,一旦一个 Watcher 被触发,ZooKeeper 都会将其从相应的存储中移除。因此,在 Watcher 的使用上,需要反复注册。这样的设计有效地减轻了服务端的压力。

客户端串行执行

客户端 Watcher 回调的过程是一个串行同步的过程,这为我们保证了顺序,同时,需要注意的一点是,一定不能因为一个 Watcher 的处理逻辑影响了整个客户端的 Watcher 回调,所以,客户端 Watcher 的实现类要另开一个线程进行处理业务逻辑,以便给其他的 Watcher 调用让出时间。

轻量

WatcherEvent 是 ZooKeeper 整个 Watcher 通知机制的最小通知单元,这个数据结构中只包含三部分内容:通知状态、事件类型和节点路径。也就是说,Watcher 通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。例如针对 NodeDataChanged 事件,ZooKeeper 的Watcher 只会通知客户端指定数据节点的数据内容发生了变更,而对于原始数据以及变更后的新数据都无法从这个事件中直接获取到,而是需要客户端主动重新去获取数据——这也是 ZooKeeper 的 Watcher 机制的一个非常重要的特性。  

 

zookeeper节点类型

 

持久节点(PERSISTENT

所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。

持久顺序节点(PERSISTENT_SEQUENTIAL

这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。

在创建节点的时候只需要传入节点 “/test_”,这样之后,zookeeper自动会给”test_”后面补充数字。

临时节点(EPHEMERAL

和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。

这里还要注意一件事,就是当你客户端会话失效后,所产生的节点也不是一下子就消失了,也要过一段时间,大概是10秒以内,可以试一下,本机操作生成节点,在服务器端用命令来查看当前的节点数目,你会发现客户端已经stop,但是产生的节点还在。

临时顺序节点(EPHEMERAL_SEQUENTIAL

此节点是属于临时节点,不过带有顺序,客户端会话结束节点就消失。下面是一个利用该特性的分布式锁的案例流程。

(1)客户端调用create()方法创建名为“locknode/

guid-lock-”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。

(2)客户端调用getChildren(“locknode”)方法来获取所有已经创建的子节点,注意,这里不注册任何Watcher。

(3)客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点序号最小,那么就认为这个客户端获得了锁。

(4)如果在步骤3中发现自己并非所有子节点中最小的,说明自己还没有获取到锁。此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时注册事件监听。

(5)之后当这个被关注的节点被移除了,客户端会收到相应的通知。这个时候客户端需要再次调用getChildren(“locknode”)方法来获取所有已经创建的子节点,确保自己确实是最小的节点了,然后进入步骤3。

 

Zookeeper节点包含的数据

 

czxid:创建节点的事务的zxid

mzxid:对znode最近修改的zxid

ctime:以距离时间原点(epoch)的毫秒数表示的znode创建时间

mtime:以距离时间原点(epoch)的毫秒数表示的znode最近修改时间

version:znode数据的修改次数

cversion:znode子节点修改次数

aversion:znode的ACL修改次数

ephemeralOwner:如果znode是临时节点,则指示节点所有者的会话ID;如果不是临时节点,则为零。

dataLength:znode数据长度。

numChildren:znode子节点个数。

 

 

 

 

 

 

 

 

 

 

 

 

Zookeeper的应用场景

 

1、1. 统一命名服务(Name Service)

分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,通常情况下用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构,既对人友好又不会重复。Zookeeper 的 Name Service 与 JNDI 能够完成的功能是差不多的,它们都是将有层次的目录结构关联到一定资源上,但是 Zookeeper 的 Name Service 更加是广泛意义上的关联,也许你并不需要将名称关联到特定资源上,你可能只需要一个不会重复名称,就像数据库中产生一个唯一的数字主键一样。Name Service 已经是 Zookeeper 内置的功能,你只要调用 Zookeeper 的 API 就能实现。

 

2、配置管理(Configuration Management)

配置的管理在分布式应用环境中很常见,如osp 接口都接入了配置中心。配置信息完全交给 Zookeeper 来管理,将配置信息保存在 Zookeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中。

 

3. 集群管理(Group Membership)

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

Zookeeper 不仅能够帮你维护当前的集群中机器的服务状态,而且能够帮你选出一个“总管”,让这个总管来管理集群,这就是 Zookeeper 的另一个功能 Leader Election。

它们的实现方式都是在 Zookeeper 上创建一个 EPHEMERAL 类型的目录节点,然后每个 Server 在它们创建目录节点的父目录节点上调用 getChildren(String path, boolean watch) 方法并设置 watch 为 true,由于是 EPHEMERAL 目录节点,当创建它的 Server 死去,这个目录节点也随之被删除,所以 Children 将会变化,这时 getChildren上的 Watch 将会被调用,所以其它 Server 就知道已经有某台 Server 死去了。新增 Server 也是同样的原理。

Zookeeper 如何实现 Leader Election,也就是选出一个 Master Server。和前面的一样每台 Server 创建一个 EPHEMERAL 目录节点,不同的是它还是一个 SEQUENTIAL 目录节点,所以它是个 EPHEMERAL_SEQUENTIAL 目录节点。之所以它是 EPHEMERAL_SEQUENTIAL 目录节点,是因为我们可以给每台 Server 编号,我们可以选择当前是最小编号的 Server 为 Master,假如这个最小编号的 Server 死去,由于是 EPHEMERAL 节点,死去的 Server 对应的节点也被删除,所以当前的节点列表中又出现一个最小编号的节点,我们就选择这个节点为当前 Master。这样就实现了动态选择 Master,避免了传统意义上单 Master 容易出现单点故障的问题。

 

4、共享锁(Locks)

共享锁在同一个进程中很容易实现,但是在跨进程或者在不同 Server 之间就不好实现了。Zookeeper 却很容易实现这个功能,实现方式也是需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点,然后调用 getChildren方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点,如果正是自己创建的,那么它就获得了这个锁,如果不是那么它就调用exists(String path, boolean watch) 方法并监控 Zookeeper 上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。

 

5. 队列管理

Zookeeper 可以处理两种类型的队列:

当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。

队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。

同步队列用 Zookeeper 实现的实现思路如下:

       创建一个父目录 /synchronizing,每个成员都监控标志(Set Watch)位目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列,加入队列的方式就是创建 /synchronizing/member_i 的临时目录节点,然后每个成员获取 / synchronizing 目录的所有目录节点,也就是 member_i。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建 /synchronizing/start。

 

Zookeeper的选举机制

  1. 全新数据的情况下的zookeeper的选举机制

场景:新启动的zookeeper集群

假设有五台服务器组成的zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的.假设这些服务器依序启动

1) 服务器1启动,此时只有它一台服务器启动了,它发出去的报没有任何响应,所以它的选举状态一直是LOOKING状态

2) 服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1,2还是继续保持LOOKING状态.

3) 服务器3启动,根据前面的理论分析,服务器3成为服务器1,2,3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的leader.

4) 服务器4启动,根据前面的分析,理论上服务器4应该是服务器1,2,3,4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟.

5) 服务器5启动,同4一样,当小弟.

 

  1. 非全新数据的情况下的选举机制

场景:当zookeeper运行了一段时间之后,有机器down掉,重新选举时

需要加入数据id、leader id和逻辑时钟(投票次数)。

数据id:数据每次更新都会更新id。

Leader id:配置的myid中的值,每个机器一个。

逻辑时钟:这个值从0开始递增,每次选举对应一个值,也就是说:  如果在同一次选举中,那么这个值应该是一致的 ;

选举的标准就变成:

1、逻辑时钟小的选举结果被忽略,重新投票

2、统一逻辑时钟后,数据id大的胜出

3、数据id相同的情况下,leaderid大的胜出

 

 

Zookeeper的工作原理

1) 首先每个服务器读取配置文件和数据文件,根据serverid知道本机对应的配置(就是前面那些地址和端口),并且将历史数据加载进内存中.

2) 集群中的服务器开始根据前设定的端口监听集群中其他服务器的请求,并且把自己选举的leader也通知其他服务器,来来往往几回,选举出集群的一个leader.

3) 选举完leader其实还不算是真正意义上的”leader”,因为到了这里leader还需要与集群中的其他服务器同步数据,如果这一步出错,将返回2)中重新选举leader.在leader选举完毕之后,集群中的其他服务器称为”follower”,也就是都要听从leader的指令.

4) 到了这里,集群中的所有服务器,不论是leader还是follower,大家的数据都是一致的了,可以开始接收客户端的连接了.如果是读类型的请求,那么直接返回就是了,因为并不改变数据;否则,都要向leader汇报,如何通知leader呢?就是通过leader_listen_port.leader收到这个修改数据的请求之后,将会广播给集群中其他follower,当超过一半数量的follower有了回复,那么就相当于这个修改操作了,这时leader可以告诉之前的那台服务器可以给客户端一个回应了.

 

 

 

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部