1、zk是什么
Zookeeper 是 Google 的 Chubby一个开源的实现,是 Hadoop 的分布式协调服务。它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等。
2、zk的优点
- 最终一致性:为客户端展示的是同一个视图
- 可靠性:如果消息被一台服务器接收,那么它将被所有的服务器接收
- 实时性:zk不能保证两个客户端能同时得到刚更新的数据,如果需要最新的数据,应在在读数据之前调用sync()接口
- 独立性:各个client之间互不干预
- 原子性:更新智能成功或失败,没有中间状态
- 顺序性:所有Server,同一消息发布顺序一致
3、使用场景
配置管理、名字服务、分布式锁、集群管理
4、数据模型
Zookeeper会维护一个具有层次关系的数据结构,它非常类似于一个标准的文件系统,如图所示:
Zookeeper这种数据结构有如下这些特点:
-
每个子目录项如NameService都被称作为znode,这个znode是被它所在的路径唯一标识,如Server1这个znode的标识为/NameService/Server1。
-
znode可以有子节点目录,并且每个znode可以存储数据,注意EPHEMERAL(临时的)类型的目录节点不能有子节点目录。
-
znode是有版本的(version),每个znode中存储的数据可以有多个版本,也就是一个访问路径中可以存储多份数据,version号自动增加。
-
znode的类型:
- Persistent 节点,一旦被创建,便不会意外丢失,即使服务器全部重启也依然存在。每个 Persist 节点即可包含数据,也可包含子节点。
- Ephemeral 节点,在创建它的客户端与服务器间的 Session 结束时自动被删除。服务器重启会导致 Session 结束,因此 Ephemeral 类型的 znode 此时也会自动删除。
- Non-sequence 节点,多个客户端同时创建同一 Non-sequence 节点时,只有一个可创建成功,其它匀失败。并且创建出的节点名称与创建时指定的节点名完全一样。
- Sequence 节点,创建出的节点名在指定的名称之后带有10位10进制数的序号。多个客户端创建同一名称的节点时,都能创建成功,只是序号不同。
-
znode可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个是Zookeeper的核心特性,Zookeeper的很多功能都是基于这个特性实现的。
-
zxid:每次对Zookeeper的状态的改变都会产生一个zxid(ZooKeeper Transaction Id),zxid是全局有序的,如果zxid1小于zxid2,则zxid1在zxid2之前发生。
-
节点不支持部分读写,而是一次性完整读写
5、相关机制
5.1 Session
Client和Zookeeper集群建立连接,整个session状态变化如图所示:
如果Client因为Timeout和Zookeeper Server失去连接,client处在CONNECTING状态,会自动尝试再去连接Server,如果在session有效期内再次成功连接到某个Server,则回到CONNECTED状态。
注意:如果因为网络状态不好,client和Server失去联系,client会停留在当前状态,会尝试主动再次连接Zookeeper Server。client不能宣称自己的session expired,session expired是由Zookeeper Server来决定的,client可以选择自己主动关闭session。
5.2 Watch
Zookeeper watch是一种监听通知机制。Zookeeper所有的读操作getData(), getChildren()和 exists()都可以设置监视(watch),监视事件可以理解为一次性的触发器。
Watch的三个关键点:
5.2.1 One-time trigger(一次性触发)
当设置监视的数据发生改变时,该监视事件会被发送到客户端。
例如,如果客户端调用了getData(“/znode1”, true) 并且稍后 /znode1 节点上的数据发生了改变或者被删除了,客户端将会获取到 /znode1 发生变化的监视事件,而如果 /znode1 再一次发生了变化,除非客户端再次对/znode1 设置监视,否则客户端不会收到事件通知。
5.2.2 Sent to the client(发送至客户端)
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)。
网络延迟或者其他因素可能导致不同的客户端在不同的时刻感知某一监视事件,但是不同的客户端所看到的一切具有一致的顺序。
5.2.3 The data for which the watch was set(被设置 watch 的数据)
这意味着znode节点本身具有不同的改变方式。你也可以想象 Zookeeper 维护了两条监视链表:数据监视和子节点监视(data watches and child watches) ,getData() 和exists()设置数据监视,getChildren()设置子节点监视。或者你也可以想象 Zookeeper 设置的不同监视返回不同的数据,getData() 和 exists() 返回znode节点的相关信息,而getChildren() 返回子节点列表。
因此,setData() 会触发设置在某一节点上所设置的数据监视(假定数据设置成功),而一次成功的create() 操作则会触发当前节点上所设置的数据监视以及父节点的子节点监视。一次成功的 delete操作将会触发当前节点的数据监视和子节点监视事件,同时也会触发该节点父节点的child watch。
Zookeeper 中的监视是轻量级的,因此容易设置、维护和分发。当客户端与 Zookeeper 服务器失去联系时,客户端并不会收到监视事件的通知,只有当客户端重新连接后,若在必要的情况下,以前注册的监视会重新被注册并触发,对于开发人员来说这通常是透明的。
只有一种情况会导致监视事件的丢失,即:通过exists()设置了某个znode节点的监视,但是如果某个客户端在此znode节点被创建和删除的时间间隔内与zookeeper服务器失去了联系,该客户端即使稍后重新连接 zookeeper服务器后也得不到事件通知。
6、选举算法
Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。
6.1 basic paxos算法
6.1.1 basic paxos角色
Paxos将系统中的角色分为提议者 (Proposer),决策者 (Acceptor),和最终决策学习者 (Learner):
- Proposer: 提出提案 (Proposal)。Proposal信息包括提案编号 (Proposal ID) 和提议的值 (Value)。
- Acceptor:参与决策,回应Proposers的提案。收到Proposal后可以接受提案,若Proposal获得多数Acceptors的接受,则称该Proposal被批准。
- Learner:不参与决策,从Proposers/Acceptors学习最新达成一致的提案(Value)。
6.1.2 决议阶段
basic paxos算法通过一个决议分为两个阶段(Learn阶段之前决议已经形成):
- 第一阶段:Prepare阶段。提议者(Proposer)向决策者(Acceptors)发出Prepare请求,决策者(Acceptors)针对收到的Prepare请求进行Promise承诺。
- 第二阶段:Accept阶段。提议者(Proposer)收到多数决策者(Acceptors)承诺的Promise后,向决策者(Acceptors)发出Propose请求,决策者(Acceptors)针对收到的Propose请求进行Accept处理。
- 第三阶段:Learn阶段。提议者(Proposer)在收到多数决策者(Acceptors)的Accept之后,标志着本次Accept成功,决议形成,将形成的决议发送给所有最终决策学习者 (Learner)。
6.1.3 承诺及应答规则
basic paxos算法流程中的每条消息描述如下:
-
Prepare: Proposer生成全局唯一且递增的Proposal ID (可使用时间戳加Server ID),向所有Acceptors发送Prepare请求,这里无需携带提案内容,只携带Proposal ID即可。
-
Promise: Acceptors收到Prepare请求后,做出“两个承诺,一个应答”。
两个承诺:
- 不再接受Proposal ID小于等于(注意:这里是<= )当前请求的Prepare请求。
- 不再接受Proposal ID小于(注意:这里是< )当前请求的Propose请求。
一个应答:
- 不违背以前作出的承诺下,回复已经Accept过的提案中Proposal ID最大的那个提案的Value和Proposal ID,没有则返回空值。
-
Propose: Proposer 收到多数Acceptors的Promise应答后,从应答中选择Proposal ID最大的提案的Value,作为本次要发起的提案。如果所有应答的提案Value均为空值,则可以自己随意决定提案Value。然后携带当前Proposal ID,向所有Acceptors发送Propose请求。
-
Accept: Acceptor收到Propose请求后,在不违背自己之前作出的承诺下,接受并持久化当前Proposal ID和提案Value。
-
Learn: Proposer收到多数Acceptors的Accept后,决议形成,将形成的决议发送给所有Learners。
6.2 fast paxos算法
从一般的Client/Server来考虑,Client其实承担了Proposer和Learner的作用,而Server则扮演Acceptor的角色。
6.2.1 fast paxos角色
- Client/Proposer/Learner:负责提案并执行提案
- Coordinator:Proposer协调者,可为多个,Client通过Coordinator进行提案
- Leader:在众多的Coordinator中指定一个作为Leader
- Acceptor:负责对Proposal进行投票表决
6.2.2 算法流程
fast paxos基于basic Paxos做了两点改进:
- 针对每一个要确定的值,运行一次Paxos算法实例(Instance),形成决议。每一个Paxos实例使用唯一的Instance ID标识。
- 在所有Proposers中选举一个Leader,由Leader唯一地提交Proposal给Acceptors进行表决。这样没有Proposer竞争,解决了活锁问题。在系统中仅有一个Leader进行Value提交的情况下,Prepare阶段就可以跳过,从而将两阶段变为一阶段,提高效率。
fast paxos首先需要选举Leader,Leader的确定也是一次决议的形成,所以可执行一次Basic Paxos实例来选举出一个Leader。选出Leader之后只能由Leader提交Proposal,在Leader宕机之后服务临时不可用,需要重新选举Leader继续服务。在系统中仅有一个Leader进行Proposal提交的情况下,Prepare阶段可以跳过。
fast paxos通过改变Prepare阶段的作用范围至后面Leader提交的所有实例,从而使得Leader的连续提交只需要执行一次Prepare阶段,后续只需要执行Accept阶段,将两阶段变为一阶段,提高了效率。为了区分连续提交的多个实例,每个实例使用一个Instance ID标识,Instance ID由Leader本地递增生成即可。
fast paxos允许有多个自认为是Leader的节点并发提交Proposal而不影响其安全性,这样的场景即退化为Basic Paxos。
7、工作原理
在zookeeper的集群中,各个节点共有下面3种角色和4种状态:
- 角色:leader,follower,observer
- 状态:leading,following,observing,looking
7.1 zk中节点的角色
- 领导者(leader):负责进行投票的发起和决议、更新系统状态
- 跟随者(follower):用于接收客户端请求并且返回响应结果,以及在选领导者的过程中参与投票
- 观察者(observer):用户接收客户端请求,并且将「写请求」转发给领导者,只同步领导者的状态,但「不参与选举领导者的投票过程」,观察者的作用主要是分担系统的「读压力」
7.2 zk中节点的状态
-
LOOKING:当前Server不知道leader是谁,正在搜寻。
-
LEADING:当前Server即为选举出来的leader。
-
FOLLOWING:leader已经选举出来,当前Server与之同步。
-
OBSERVING:observer的行为在大多数情况下与follower完全一致,但是他们不参加选举和投票,而仅仅接受(observing)选举和投票的结果。
7.3 Zab协议
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议(ZooKeeper Atomic Broadcast protocol)。
Zab协议有两种模式,它们分别是恢复模式(Recovery选主)和广播模式(Broadcast同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
7.4 zxid事务id号
为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
参考链接:
https://zhuanlan.zhihu.com/p/31780743
https://www.cnblogs.com/linbingdong/p/6253479.html
https://www.cnblogs.com/luxiaoxun/p/4887452.html
https://blog.csdn.net/u010039929/article/details/70171672
如有不对,烦请指出,感谢~