zookeeper(一) 基础

Posted by ZhouJ000 on February 1, 2019

zookeeper(一) 基础
zookeeper(二) Java API
zookeeper(三) 管理

ZooKeeper 是一个开源的分布式协调服务,由雅虎创建,是 Google Chubby 的开源实现。分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、配置维护,名字服务、分布式同步、分布式锁和分布式队列等功能

功能

ZooKeeper的系统功能围绕一条主线:它可以在分布式系统中协作多个任务。一个协作任务是指一个包含多个进程的任务,这个任务可以是为了协作或是为了管理竞争。协作意味着多个进程需要一同处理某些事情,一些进程采取某些行动使得其他进程可以继续工作

ZooKeeper是一个高可用的分布式数据管理与系统协调框架。基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题。它能提供基于类似于文件系统的目录节点树方式的数据存储,用来维护和监控存储的数据的状态变化,通过监控这些数据状态的变化,从而达到基于数据的集群管理。简单的说,ZooKeeper=文件系统(znode数据结构)+通知机制(watch)

ZooKeeper使用在HBase、Kafka、Solr等中。当决定使用ZooKeeper来设计应用时,最好将应用数据和协同数据独立开。ZooKeeper的客户端API功能强大,其中包括有:
1、保障强一致性、有序性和持久性
2、实现通用的同步原语的能力
3、提供一种简单的并发处理机制

ZooKeeper之前其他一些采用分布式锁管理器或者分布式数据库来实现协作,ZooKeeper也从中借鉴很多概念,但ZooKeeper更专注于任务协作,并不提供任何锁的接口或通用存储数据接口,同时没有给开发者强加任何特殊的同步原语,使用起来非常灵活。整个ZooKeeper的服务器集群管理着应用协作的关键数据。不适合用于作为海量数据存储,对于这种情况应该考虑数据库和分布式文件系统等

典型应用场景

发布订阅/配置中心:发布与订阅模型,(发布者)把公用的配置/数据放到ZK节点上,供订阅者动态获取数据,订阅者在这个ZK节点上进行监听,实现配置信息的集中式管理和动态更新。一旦配置信息发生变化,每个应用程序就会收到ZooKeeper的通知,然后从ZooKeeper获取新的配置信息应用到系统中,项目不需要重启

命名服务:跟JNDI功能几乎差不多。客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等——这些我们都可以统称他们为名字(Name)。其中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。比如Dubbo中使用ZooKeeper来作为其命名服务,维护全局的服务地址列表,服务提供者启动时,提供者服务器的相关信息,包括服务接口、地址、端口等信息注册到ZK上的指定节点/dubbo/${serviceName}/providers目录下,完成了服务的发布。当消费者要消费某服务的时候,订阅/dubbo/${serviceName}/providers目录下的提供者URL地址,从zookeeper中拿到服务的所有提供者信息目录,再根据dubbo的负载均衡机制从集合中选择一个提供者。消费者还会向/dubbo/${serviceName}/consumers目录下写入自己的URL地址

集群管理:集群机器监控非常方便,要能够快速对集群中机器变化作出响应。依赖于节点监听(watch)机制和临时节点一旦客户端与服务器之间的会话失效,那么该临时节点也就被自动清除这两大特性,可以实现集群机器存活性监控,方便机器的上线与下线。除此之外可以进行Master选举,Master节点挂了之后,从节点就会接手工作成为主节点,并且保证这个节点是唯一的,这就是首脑模式,从而保证集群的高可用。

提供分布式锁:分布式环境中,不同进程之间争夺资源,实现类似于多线程中的锁的作用。得益于ZooKeeper为我们保证了数据的强一致性。锁服务可以分为两类,一个是保持独占,另一个是控制时序。保持独占就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁,通常的做法是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建/distribute_lock节点,最终成功创建的那个客户端也即拥有了这把锁。控制时序,基本类似,但是所有视图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序。这里/distribute_lock已经预先存在,客户端在它下面创建临时有序节点(sequence类型),Zk的父节点(/distribute_lock)维持一份sequence,后面的每个子节点都监听之前的子节点,保证子节点创建的时序性,从而也形成了每个客户端的全局时序

分布式队列:简单地讲有两种,一种是常规的先进先出队列,另一种是要等到队列成员聚齐之后的才统一按序执行。对于第一种先进先出队列,和分布式锁服务中的控制时序场景基本原理一致。第二种是在FIFO队列的基础上作了一个增强,通常会在/queue这个znode下预先建立一个/queue/num节点,并且赋值为n(或者直接给/queue赋值n),表示队列大小,之后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否可以开始执行了

分布式通知/协调ZooKeeper中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系统能够收到通知,并作出相应处理。比如一种心跳检测机制、一种系统调度模式、一种工作汇报模式。总之,使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合

负载均衡:是指软负载均衡。在分布式环境中,为了保证高可用性,通常同一个应用或同一个服务的提供方都会部署多份,达到对等服务。而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑,其中比较典型的是消息中间件中的生产者,消费者负载均衡。消息中间件中发布者和订阅者的负载均衡,KafkaMQ和metaq都是通过zookeeper来做到生产者、消费者的负载均衡

组服务:对于服务注册、获取服务、创建组、加入组成员、删除组等服务,通过ZooKeeper心跳机制,它会去检测与其连接的一些服务器的数量以及信息,什么时候连上ZooKeeper,或什么时候断开都由其心跳机制完成

特点

分布式系统中的进程通信有两种选择:一是直接通过网络进行信息交换;二是读写某些共享存储。ZooKeeper使用共享存储模型来实现应用间的协作和同步原语。对于共享存储本身,又需要在进程和存储间进行网络通信。我们强调网络通信的重要性,因为它是分布式系统中并发设计的基础

在真实的系统中,需要特别注意以下三点:
1、消息延迟:消息传输可能发生任意延迟,比如网络拥堵。这种任意延迟可能会导致不可预期的后果
2、处理器性能:操作系统的调度和超载也可能导致消息处理的任意延迟。当一个进程向另一个进程发送消息时,整个消息的延时时间约等于发送端耗时、传输时间、接收端耗时的总合。如果发送或接受过程需要调度时间进行处理,消息延时会更高
3、时钟偏移:处理器时钟并不可靠,它们之间也会发生任意偏移。因此依赖处理器时钟也许会导致错误的决策
关于这些问题的一个重要结果是,在实际情况中很难判断一个进程是崩溃了还是某些因素导致了延时。没有收到一个进程发送的消息,可能是该进程已经崩溃,或是最新消息发生了网络延迟,或是其他情况导致进程延迟,或是进程时钟发生了偏移。我们无法确定一个被称为异步的系统中的这些区别。ZooKeeper简化了这些问题的处理,并不是完全消除了这些问题,而是将这些问题在应用服务层面上完全透明化,使得这些问题更容易处理

对于分布式中广泛用到的“主-从”模式,也必须解决以下三个关键问题:
1、主节点崩溃:如果主节点发送错误并失效,系统将无法分配新的任务或重新分配已失败的任务。一般会使用备份主节点进行故障转移,需要恢复状态,除了状态恢复,还需要考虑到脑裂的情况
2、从节点崩溃:已分配的任务将无法完成。主节点也必须能检测到从节点的崩溃,并确定有效从节点以便重新派发崩溃节点的任务,对于任务还可能有必要执行某些恢复过程来清除之前不确定的状态
3、通信故障:如果主节点和从节点之间无法进行信息交换,从节点将无法得知新任务的分配,新任务的分配也可能会导致两个从节点执行相同的任务。另一个重要的问题就是对锁等同步原语的影响
从上面的问题就能知道“主-从”模式的需求:
1、主节点选举:使得主节点可以给从节点分配任务
2、崩溃检测:主节点必须具有检测从节点崩溃或失去连接的能力
3、组成员关系管理:主节点必须具有知道哪一个从节点可以执行任务的能力
4、元数据管理:主节点和从节点必须具有通过某种可靠的方式来保存分配状态和执行状态的能力
ZooKeeper提供了实现这些原语的关键机制,开发者可以通过这些实现一个最合适需求、更加关注应用逻辑的分布式应用

对于CAP定律,该定律指出一个分布式系统,不能同时满足一致性、可用性和分区容错性这三种属性。因此ZooKeeper的设计尽可能满足一致性和可用性,当然在发生网络分区时,ZooKeeper也提供了只读能力。因此我们无法拥有一个理想的故障容错的、分布式的、真实环境存在的系统来处理可能发生的所有问题,完美的解决方案是不存在的,但是ZooKeeper提供了一个优雅的框架来处理问题。例如可以假设时钟在某种范围内是同步的,也可以牺牲一些网络分区容错的能力并认为其一直是一致的,当一个进程运行中也许多次因无法确定系统中的状态而被认定为已经发生故障。虽然都是折中方案,但这些折中方案允许我们建立一些印象非常深刻的分布式系统

因此综合来讲,ZooKeeper有以下特点
1、是一个精简的文件系统
2、提供了丰富的“构件”,这些构件可以实现很多协调数据结构和协议的操作。例如:分布式队列、分布式锁以及一组同级节点的“领导者选举”算法
3、是高可用的,它本身的稳定性是相当之好,分布式集群完全可以依赖zookeeper集群的管理,利用zookeeper避免分布式系统的单点故障的问题
4、采用了松耦合的交互模式,让参入的进程不在了解其他进程的(或网络)的情况下能够彼此发现并进行交互,参入的各方甚至不必同时存在,只要在zookeeper留下一条消息,在该进程结束后,另外一个进程还可以读取这条信息,从而解耦了各个节点之间的关系
5、为集群提供了一个共享存储库,集群可以从这里集中读写共享的信息
6、采用的是观察者的设计模式,存储和管理发布的数据,接受观察者的注册,一旦这些数据的状态发生变化ZooKeeper就将负责通知注册的观察者做出相应的反应

基础

znode

zk-znode-tree

ZooKeeper操作和维护小型的数据节点,这些节点被称为znode,采用类似于文件系统的层级树状结构进行管理。znode节点可能含有数据,也可能没有。如果一个znode节点包含任何数据,那么数据存储为字节数组(byte array),字节数组的具体格式特定于每个应用的实现。ZooKeeper并不直接提供解析的支持,我们可以使用例如Protocol Buffers、Thrift、Avro或MessagePack等序列化包来方便处理保存于znode节点的数据格式,不过一般以UTF-8或ASCII编码的字符串已经够用了

和文件系统一样,我们可以自由的增加、删除znode,在一个znode下增加、删除子znode,ZooKeeper的API暴露了以下方法:

create /path data
delete /path
exists /path 
setData /path data 	  需要注意zk不允许局部写入或读取znode节点数据,内容会被整个替换或全部读取出来
getData /path 
getChildren /path

当新建znode时候,还需要指定该节点的类型(mode),不同的类型决定了znode节点的行为方式:
持久(persistent)节点:持久的znode,只能通过调用delete来进行删除
临时(ephemeral)节点:当创建该节点的客户端崩溃或关闭了与ZooKeeper的连接时,这个节点就会被删除
有序(sequential)节点:一个有序znode节点被分配唯一一个单调递增的整数。当创建有序节点时,一个序号会被追加到路径之后。有序节点也分为持久的和临时的,因此实际上是持久有序(persistent_sequential)节点临时有序(ephemeral_sequential)节点

每个znode都有一个版本号,它随着每次数据变化而自增。两个API操作可以有条件地执行:setData和delete。这两个调用以版本号作为传入参数,只有当传入参数的版本号与服务器上的版本号一致时调用才会成功。当多个ZK客户端对同一个znode进行操作时,版本的使用会显得尤为重要

使用get命令获取指定节点数据时,同时也会返回该节点的状态信息,称为stat,包含有:
1、czxid:节点创建时的zxid
2、mzxid:节点最新一次更新发生时的zxid
3、ctime:节点创建时的时间戳
4、mtime:节点最新一次更新发生时的时间戳
5、dataVersion:节点数据的更新次数
6、cversion:其子节点的更新次数
7、aclVersion:节点ACL(授权信息)的更新次数
8、phemeralOwner:如果该节点为ephemeral节点, ephemeralOwner值表示与该节点绑定的session id:如果该节点不是ephemeral节点, ephemeralOwner值为0
9、dataLength:节点数据的字节数
10、numChildren:子节点个数
注:在ZooKeeper中,能改变ZooKeeper服务器状态的操作称为事务操作。对应每一个事务请求,ZooKeeper都会为其分配一个全局唯一的递增事务ID,称为zxid,通常是一个64位的数字。由于zxid的递增性质,如果zxid1小于zxid2,那么zxid1肯定先于zxid2发生,因此从这些zxid中可以间接地识别出ZooKeeper处理这些事务操作请求的全局顺序

Watcher

为了替换常见的轮询机制,ZooKeeper选择了基于通知(notification)的机制:客户端向ZK注册需要接受通知的znode,通过对znode设置监视点(watch)来接受通知。监视点是一个单次触发(一次性的)的操作,即当被设置了监视点的数据发生了改变的时候,监视点会触发一个通知给设置监视点的客户端,之后ZooKeeper会将其从相应的存储中移除。因此为了接受多个通知,客户端必须在每次通知后设置一个新的监视点

通知机制的一个重要保障是,对同个znode的操作,先向客户端传送通知,然后再对该节点进行变更。如果客户端对一个znode设置了监视点,而该znode发生了两个连续更新。第一次更新后,客户端在观察第二次变化前就接受到了通知,然后读取znode中的数据,再设置新的监视点。由于设置新的监视点,没有接受到第二次变更的通知,因此为了观察这个变更,在设置新的监视点前,客户端实际上需要读取节点的状态,通过在设置监视点之前读取ZooKeeper的状态,使得客户端不会错过任何变更

ZooKeeper客户端和服务端是通过Socket进行通信的,由于网络存在故障,所以监视事件很有可能不会成功地到达客户端,监视事件是异步发送至监视者的,ZooKeeper本身提供了保序性(ordering guarantee):即客户端只有首先看到了监视事件后,才会感知到它所设置监视的znode发生了变化(a client will never see a change for which it has set a watch until it first sees the watch event)。网络延迟或者其他因素可能导致不同的客户端在不同的时刻感知某一监视事件,但是不同的客户端所看到的一切具有一致的顺序

ZooKeeper可以定义不同类型的通知,这依赖于设置监视点对应的通知类型:
1、可以注册watcher的方法:getData、exists、getChildren
2、可以触发watcher的方法:create、delete、setData(连接断开的情况下触发的watcher会丢失)
注:一个watcher实例是一个回调函数,被回调一次后就被移除了。如果还需要关注数据的变化,需要再次注册watcher。特别的,New ZooKeeper时注册的watcher叫default watcher,它不是一次性的,只对client的连接状态变化作出反应

操作与事件类型的对应关系
操作 event For “/path” 设置监视点 event For “/path/child” 设置监视点
create(“/path”) EventType.NodeCreated exists
delete(“/path”) EventType.NodeDeleted exists或getData
setData(“/path”) EventType.NodeDataChanged exists或getData
create(“/path/child”) EventType.NodeChildrenChanged(getChild) getChildren EventType.NodeCreated exists
delete(“/path/child”) EventType.NodeChildrenChanged(getChild) getChildren EventType.NodeDeleted exists或getData
setData(“/path/child”) EventType.NodeDataChanged exists或getData

会话Session

在对ZooKeeper集合执行任何请求前,一个客户端必须先与服务器建立会话。客户端提交的所有操作均关联在一个会话上。当一个会话因某种原因而中止时,在这个会话期间创建的临时节点将会消失

当客户端通过某一个特定语言套件来创建一个ZooKeeper句柄时,它就会通过服务器创建一个会话。客户端初始连接到集合中某一个服务器或一个独立的服务器时,客户端通过TCP协议与服务器进行连接并通信,但当会话无法与当前连接的服务器继续通信时,会话就可能转移到另一个服务器上。ZooKeeper客户端库透明地转移一个会话到不同的服务器

会话提供了顺序保障,这意味着同一个会话中的请求会以FIFO(先进先出)顺序执行。通常一个客户端只打开一个会话,因此请求都以FIFO顺序执行。如果客户端拥有多个并发的会话,FIFO顺序在多个会话之间未必能够保持。而即使一个客户端中连贯的会话并不重叠,也未必能保证FIFO顺序

会话的生命周期:
zk-session-lifecycle 其中,如果客户端与ZK服务器断开连接或无法收到服务器响应时,从CONNECTED状态转为CONNECTING状态并尝试发现其他ZK服务器,如果发现另一个服务器或重连回原服务器,服务器确定会话有效后,状态又会回到CONNECTED状态,否则声明会话过期,转换到CLOSED状态

创建一个会话时,对于设置的会话超时时间t。在ZK服务侧是允许会话被声明为超时之前的存在时间,如果经过时间t之后服务接收不到这个会话的任何消息,服务就会声明会话过期。而在客户端侧,如果经过t/3的时间未收到任何消息,客户端将向服务器发送心跳消息,在经过2t/3的时间之后,客户端开始寻找其他的服务器,这时还有t/3的时间去寻找

当尝试连接到一个不同的服务器时,非常重要的是,这个服务器的ZooKeeper状态要与最后连接的服务器的ZooKeeper状态保持最新。客户端不能连接到未发现更新而客户端却发现更新的服务器上。ZooKeeper通过在服务中排序更新操作来决定状态是否最新,ZooKeeper确保每一个变化相对于所有其他已经执行的更新是完全有序的。因此,如果一个客户端在位置i上观察到一个更新,那它就不能连接到只观察到i’<i的服务器上,即事务标识符(zxid)不能小于客户端的

模式

ZooKeeper服务器端运行于两种模式下:独立模式(standalone)和仲裁模式(quorum)。独立模式就是只有一个单独的服务器,状态无法复制。而在仲裁模式下,具有一组ZooKeeper服务器,称之为ZooKeeper集合,它们之间可以进行状态复制,并同时服务于客户端的请求。对于所有机器间可以做数据复制,有以下好处:容错性好、提高系统的扩展能力、就近访问提高性能

对于客户端读写访问的透明度来看,数据复制集群系统可以分为写主(WriteMaster)和写任意(Write Any),对于ZooKeeper来说,采用的方式是写任意。因此通过增加机器,读的吞吐能力和响应能力扩展性非常好,而对于写,由于ZooKeeper的znode变更是要过半数投票通过的,随着机器的增加,由于网络消耗等原因必然导致投票成本增加,从而导致写性能的下降(因此有了observer)

仲裁模式下,ZooKeeper复制集群中的所有服务器的数据树。但如果让一个客户端等待每个服务器完成数据保存后再继续,延迟问题无法接受。在公共管理领域,法定人数是指进行一项投票所需的立法者的最小数量。在ZooKeeper中则是为了使ZooKeeper工作必须有效运行的服务器的最小数量。这个数字也是服务器告诉客户端安全保存数据前,需要保存客户端数据的服务器的最小个数。选择法定人数准确是非常重要的事,法定人数需要保证不管系统发生延迟或崩溃,服务主动确认的任何更新请求需要保持下去,直到另一个请求代替它。一般使用多数方案,即可以容许f个服务器崩溃,f小于集合中服务器数量的一半

仲裁模式下,需要在zoo.cfg配置文件中配置多个server集合信息,比如server.1=127.0.0.1:2222:2223。每个server.n项指定了编号为n的ZooKeeper服务器使用的地址和端口号,两个端口号分别用于仲裁通信和群首选举,然后修改不同的clientPort。最后分别设置data目录,然后在data目录下创建名为myid的文件来获取服务器ID信息即可

启动服务数量大于仲裁的法定人数后,服务可以开始使用,会推选出一个群首。客户端访问集群时,以随机顺序连接到连接串中的服务器,这样可以用ZooKeeper来实现一个简单的负载均衡。除了连接串以外,客户端不用关心ZooKeeper服务由多少个服务器组成,可以使用zkCli模拟客户端zkCli.sh -server 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183

启动与关闭

1、修改zoo.cfg配置文件中的dataDir路径和dataLogDir路径
2、zookeeper用jetty服务器占用8080端口,如果被占用,需要在zoo.cfg配置文件中添加admin.serverPort=<port>或者在启动中添加-Dzookeeper.admin.serverPort=<port>参数。也可以选择停用jetty服务,添加-Dzookeeper.admin.enableServer=false
3、使用zkServer.sh start或win下的zkServer.cmd启动ZK

通过zkCli可以建立一个会话,客户端尝试连接到localhost/127.0.0.1:2181。客户端连接成功后,服务器开始初始化这个新会话,会话初始化完成后,服务器向客户端发送一个SyncConnected事件

Connecting to localhost:2181
2019-02-01 11:21:43,022 [myid:] - INFO  [main:Environment@100] - Client environment:...

... [client environment info ...] ...

2019-02-01 11:21:43,037 [myid:] - INFO  [main:ZooKeeper@438] - Initiating client connection, connectString=localhost:218
1 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@277050dc
Welcome to ZooKeeper!
2019-02-01 11:21:43,131 [myid:] - INFO  [main-SendThread(0:0:0:0:0:0:0:1:2181):ClientCnxn$SendThread@1032] - Opening soc
ket connection to server 0:0:0:0:0:0:0:1/0:0:0:0:0:0:0:1:2181. Will not attempt to authenticate using SASL (unknown erro
r)
2019-02-01 11:21:43,147 [myid:] - INFO  [main-SendThread(0:0:0:0:0:0:0:1:2181):ClientCnxn$SendThread@876] - Socket conne
ction established to 0:0:0:0:0:0:0:1/0:0:0:0:0:0:0:1:2181, initiating session
JLine support is enabled
2019-02-01 11:21:43,226 [myid:] - INFO  [main-SendThread(0:0:0:0:0:0:0:1:2181):ClientCnxn$SendThread@1299] - Session est
ablishment complete on server 0:0:0:0:0:0:0:1/0:0:0:0:0:0:0:1:2181, sessionid = 0x168a70e8fbf0000, negotiated timeout =
30000

WATCHER::

WatchedEvent state:SyncConnected type:None path:null

1、可以使用create /workers ""创建一个znode
2、使用ls /查看根root下的所有znode
3、通过delete /workers删除这个空的znode。(rmr命令可以递归删除有children的znode)
4、最后quit退出,会话关闭:Session closed,EventThread shut down for session

最后关闭ZooKeeper服务器,使用zkServer.sh stop,或者win下直接关闭CMD或终止

简单的主从模式

一般使用“主-从”模式,包含三个角色:
1、主节点:负责监视新的从节点和任务,分配任务给可用的从节点
2、从节点:通过系统注册自己,以确保主节点看到它们可以执行任务,然后开始监视新任务
3、客户端:创建新任务并等待系统的响应

主节点
因为只有一个进程会成为主节点,所以一个进程成为ZooKeeper的主节点后必须锁定管理权。为此,进程需要创建一个临时的znode,名为/master。可以使用create -e /master "127.0.0.1:3334"命令(在还在znode中添加了主机信息,这并不是必需的,需要注意的是在ZK的3.5.0版本之前必须有4个参数,否则是会抛出数组越界异常的)。这时候其他的进程将成为备份主节点,当尝试创建/master时会提示Node already exists: /master已经存在,由于是临时节点,因此如果主节点崩溃后,备份主节点需要接替活动的主节点角色,为了检测这些就需要在/master节点上设置一个监视点,stat /master true,当主节点崩溃或关闭会话时,临时节点删除,可以观察到WatchedEvent state:SyncConnected type:NodeDeleted path:/master,指出主节点的会话已经关闭或过期,这时/master节点已经不存在了,现在备份主节点需要通过再次创建/master节点来成为活动主节点

为了分配任务,可以分别创建/workers、/tasks、/assign三个重要的父znode,例如create /tasks "",可以通过这3个持久化节点知道哪些从节点当前有效,哪些任务需要被分配。可以使用ls /tasks true,使用true参数,设置对应znode的子节点的监视点,知道子节点的变化

从节点
从节点需要告知主节点可以执行任务。从节点通过在/workers子节点下创建临时的znode来进行通知,并在子节点中使用主机名来标识自己。一旦从节点在/workers下创建了znode,主节点就会观察到通知信息。之后,从节点需要创建/assing/worker1来接受任务分配,并通过ls /assign/worker1 true来监视这个节点的变化

客户端
向系统添加任务,使用-s来创建一个有序的znode,本质上为一个队列。比如create -s /tasks/task- "my task1",这时就创建了task-0000000000子节点。执行任务的子节点执行完毕后,会创建一个znode来表示任务状态,因此客户端可以通过ls /tasks/task-0000000000 true来查看任务状态的znode是否创建来确定任务是否执行完毕。而主节点则会检查新任务的创建,获取可用的从节点列表,之后分配这个任务到对应的/assign/worker1下创建task-0000000000,从节点完成后在/tasks/task-0000000000下创建名为status的表状态znode,内容标识为done