目录
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等。Zookeeper是hadoop的一个子项目,其发展历程无需赘述。在分布式应用中,由于工程师不能很好地使用锁机制,以及基于消息的协调机制不适合在某些应用中使用,因此需要有一种可靠的、可扩展的、分布式的、可配置的协调机制来统一系统的状态。Zookeeper的目的就在于此。本文简单分析zookeeper的工作原理,对于如何使用zookeeper不是本文讨论的重点。
1 Zookeeper的基本概念
1.1 角色
Zookeeper中的角色主要有以下三类,如下表所示:
系统模型如图所示:
1.2 设计目的
1.最终一致性:client不论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能。
2 .可靠性:具有简单、健壮、良好的性能,如果消息m被到一台服务器接受,那么它将被所有的服务器接受。
3 .实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
4 .等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
5.原子性:更新只能成功或者失败,没有中间状态。
6 .顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
2、数据一致性与paxos 算法
2.1、解决的问题
在一个分布式系统中,由于网络通信的存在,节点可能会出现延迟、失败、或者数据包的顺序错乱等问题。在这种情况下,如何在分布式系统的多个节点之间就一个值达成一致是个十分关键且棘手的问题。这就是所谓的"分布式一致性问题"。
Paxos算法就是设计来解决这个问题的。它是一种基本的一致性协议,用于在一个可靠的分布式系统中解决一致性问题,即使在分布式系统的部分节点发生了失败,也能确保系统整体的一致性。举个简单的例子,就比如说在一个购物网站的分布式系统中,无论用户在什么情况下提交了订单,这个订单都会被所有节点正确且一致的处理。这样就确保了网站订单处理的一致性,保证了用户体验。
因此,Paxos是一个非常重要的分布式一致性协议,至今仍被许多分布式系统用作底层的一致性保证。
2.2、原理
Paxos算法中有三种角色:
- Proposer:提出提议,希望得到多数派的接受。
- Acceptor:决定是否接受提议。
- Learner:学习已经达成的提议。
Paxos算法的过程主要包含两个阶段:
Prepare阶段: Proposer选择一个提议编号N,并将编号N及提议内容发送给所有的Acceptor,这一步被称为“Prepare请求”。 每个Acceptor收到Prepare请求后,如果该请求的编号N是自己见过的最大的,那么就会接受这个请求,并将自己接受过的最大编号的提议回复给Proposer,这一步被称为“Promise回复”。
Accept阶段: Proposer收到多数派Acceptor的Promise回复后,就可以进入Accept阶段。Proposer会选择N以及收到的所有Promise回复中提议编号最大的提议的值,组成新的提议,并发送给所有的Acceptor,这一步被称为“Accept请求”。 Acceptor收到Accept请求后,如果请求中的提议编号依旧是自己见过的最大的,那么就会接受这个请求,也就是接受这个提议。
通过这两个阶段,Paxos算法保证了在一个分布式系统中,尽管可能存在故障的节点,但系统仍然能够达成一致性的决定。在实际应用中,Paxos算法通常需要进行一些优化,例如Multi-Paxos,Fast Paxos等。
2.3、应用
Paxos算法设计由于理解复杂,往往在实践中很少直接使用,以下是一些使用Paxos算法的著名系统:
Google的BigTable:BigTable是Google的一个分布式存储系统,它的设计也参考了Paxos算法。
Apache Cassandra:Cassandra是一种分布式NoSQL数据库,它的设计也参考了Paxos算法。
etcd:etcd是一个为Kubernetes提供配置共享和服务发现的开源项目,内部使用Raft,一种Paxos的变种协议,来保证一致性。
3 ZooKeeper的工作原理
ZAB协议基于 Paxos 协议改进用于实现 Zookeeper 的一致性处理。ZAB 在设计实现过程中做了许多具体化的规定,比如 Leader 选举,epoch 确定和 FIFO Order 等。这使得 Paxos 的实现更好用并提高了它的性能。
Zookeeper的核心是原子广播(ZAB协议),这个机制保证了各个Server之间的同步。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态
Zab协议保证:
如果leader以T1和T2的顺序广播,那么所有的Server必须先执行T1,再执行T2。
如果任意一个Server以T1、T2的顺序commit执行,其他所有的Server也必须以T1、T2的顺序执行。
为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
每个Server在工作过程中有三种状态:
-
LOOKING:当前Server不知道leader是谁,正在搜寻
-
LEADING:当前Server即为选举出来的leader
-
FOLLOWING:leader已经选举出来,当前Server与之同步
3.1 选主流程
- ZooKeeper的选举算法是基于ZAB协议(ZooKeeper Atomic Broadcast)。
- ZooKeeper的选举算法有两种,一种是FastLeaderElection(快速选举),另一种是LeaderElection(经典选举)。默认是FastLeaderElection。
FastLeaderElection
FastLeaderElection(快速选举): FastLeaderElection是ZooKeeper的默认选举策略,它通过在选举过程中引入了逻辑时钟(ZXID),以此来减少选举过程中的网络通信次数,提高选举速度。 在FastLeaderElection过程中,每个节点将自己的状态(包括ZXID、选举轮数、节点ID等)打包成投票信息,并广播给其他所有节点。每个节点在收到投票信息后,将对方的投票信息和自己的进行比较,如果对方的ZXID、选举轮数、节点ID等都大于自己的,那么就会更新自己的投票信息,并广播给其他所有节点。这样,经过若干轮的投票,最终会有一个节点的投票信息得到超过半数的认可,这个节点就会成为Leader。
LeaderElection
LeaderElection(经典选举): LeaderElection是ZooKeeper的早期选举策略,它的选举过程比较简单,但需要多次网络通信,效率较低。 在LeaderElection过程中,每个节点都会首先将自己设置为LOOKING状态,并向其他所有节点发送投票信息。收到投票信息的节点,如果发现自己的状态为LOOKING,并且对方的节点ID大于自己的,那么就会回复一个OK信息。如果一个节点收到了超过半数的OK信息,那么这个节点就会成为Leader。
在实际应用中,由于FastLeaderElection的选举效率更高,因此被设置为了ZooKeeper的默认选举算法。
3.2 同步流程
选完leader以后,zk就进入状态同步过程。把follower的数据同步给leader的过程:
-
1. leader等待server连接;
-
2 .Follower连接leader,将最大的zxid发送给leader;
-
3 .Leader根据follower的zxid确定同步点;
-
4 .完成同步后通知follower 已经成为uptodate状态;
-
5 .Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。
流程图如下所示:
3.3 工作流程
3.3.1 Leader工作流程
Leader主要有三个功能:
-
1 .恢复数据;
-
2 .维持与Learner的心跳,接收Learner请求并判断Learner的请求消息类型;
-
3 .Learner的消息类型主要有PING消息、REQUEST消息、ACK消息、REVALIDATE消息,根据不同的消息类型,进行不同的处理。
PING消息是指Learner的心跳信息;REQUEST消息是Follower发送的提议信息,包括写请求及同步请求;ACK消息是Follower的对提议的回复,超过半数的Follower通过,则commit该提议;REVALIDATE消息是用来延长SESSION有效时间。
Leader的工作流程简图如下所示,在实际实现中,流程要比下图复杂得多,启动了三个线程来实现功能。
3.3.2 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的工作流程简图如下所示,在实际实现中,Follower是通过5个线程来实现功能的。
对于observer的流程不再叙述,observer流程和Follower的唯一不同的地方就是observer不会参加leader发起的投票。
4.Zookeeper 监视(Watches) 简介
- ZooKeeper的Watch机制,也被称为观察者模式,是一种当指定节点的数据发生改变时,能够通知客户端的机制。
- 当客户端在读取一个znode节点数据时,可以选择设置一个watch,那么当这个znode的数据发生变化或者被删除时,ZooKeeper会向设置了watch的客户端发送一个通知,告诉客户端该节点的数据已经发生了改变。此外,如果客户端设置了watch的节点所在的路径被删除,客户端也会收到一个通知。
- 这种机制可以使客户端及时得知节点数据的变化,从而进行相应的处理,比如更新本地缓存,触发某个业务逻辑等。
- 值得注意的是,ZooKeeper的watch机制是一次性的,也就是说一旦触发后,如果客户端还想继续保持对该节点的watch,需要再次对节点进行操作并设置watch。
Watch事件类型:
- ZOO_CREATED_EVENT:节点创建事件,需要watch一个不存在的节点,当节点被创建时触发,此watch通过zoo_exists()设置
- ZOO_DELETED_EVENT:节点删除事件,此watch通过zoo_exists()或zoo_get()设置
- ZOO_CHANGED_EVENT:节点数据改变事件,此watch通过zoo_exists()或zoo_get()设置
- ZOO_CHILD_EVENT:子节点列表改变事件,此watch通过zoo_get_children()或zoo_get_children2()设置
- ZOO_SESSION_EVENT:会话失效事件,客户端与服务端断开或重连时触发
- ZOO_NOTWATCHING_EVENT:watch移除事件,服务端出于某些原因不再为客户端watch节点时触发
5.负载均衡策略
Ø 轮询(RoundRobin)将请求顺序循环地发到每个服务器。当其中某个服务器发生故障,AX就把其从顺序循环队列中拿出,不参加下一次的轮询,直到其恢复正常。
Ø 比率(Ratio):给每个服务器分配一个加权值为比例,根椐这个比例,把用户的请求分配到每个服务器。当其中某个服务器发生故障,AX就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
Ø 优先权(Priority):给所有服务器分组,给每个组定义优先权,将用户的请求分配给优先级最高的服务器组(在同一组内,采用预先设定的轮询或比率算法,分配用户的请求);当最高优先级中所有服务器或者指定数量的服务器出现故障,AX将把请求送给次优先级的服务器组。这种方式,实际为用户提供一种热备份的方式。
Ø 最少连接数(LeastConnection):AX会记录当前每台服务器或者服务端口上的连接数,新的连接将传递给连接数最少的服务器。当其中某个服务器发生故障,AX就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
Ø 最快响应时间(Fast Reponse time):新的连接传递给那些响应最快的服务器。当其中某个服务器发生故障,AX就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
以上为通用的负载均衡算法,还有一些算法根据不同的需求也可能会用到,例如:
Ø 哈希算法( hash): 将客户端的源地址,端口进行哈希运算,根据运算的结果转发给一台服务器进行处理,当其中某个服务器发生故障,就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。
6.Zk在Dubbo中的作用
zk在dubbo中是服务注册与发现的注册中心,dubbo的调用过程是consumer和provider在启动的时候就和注册中心建立一个socket长连接。provider将自己的服务注册到注册中心上,注册中心将可用的提供者列表notify给consumer,consumer会将列表存储到本地缓存,consumer选举出一个要调用的提供者,去远程调用。zookeeper通过心跳机制可以检测挂掉的机器并将挂掉机器的ip和服务对应关系从列表中删除
Dubbo的将注册中心进行抽象,是得它可以外接不同的存储媒介给注册中心提供服务,有ZooKeeper,Memcached,Redis等。
7.Zk常见问题
为什么使用了ZooKeeper作为存储媒介 ?
负载均衡,单注册中心的承载能力是有限的,在流量达到一定程度的时 候就需要分流,负载均衡就是为了分流而存在的,一个ZooKeeper群配合相应的Web应用就可以很容易达到负载均衡;
资源同步,单单有负载均衡还不 够,节点之间的数据和资源需要同步,ZooKeeper集群就天然具备有这样的功能;
命名服务,这个是zk专有的特性,将树状结构用于维护全局的服务地址列表,服务提供者在启动 的时候,向ZK上的指定节点/dubbo/${serviceName}/providers目录下写入自己的URL地址,这个操作就完成了服务的发布。 其他特性还有Mast选举,分布式锁等。
为什么zookeeper集群的数目,一般为奇数个?
Leader选举算法采用了Paxos协议;
Paxos核心思想:当多数Server写成功,则任务数据写成功如果有3个Server,则两个写成功即可;如果有4或5个Server,则三个写成功即可。
Server数目一般为奇数(3、5、7)如果有3个Server,则最多允许1个Server挂掉;如果有4个Server,则同样最多允许1个Server挂掉由此,我们看出3台服务器和4台服务器的的容灾能力是一样的,所以为了节省服务器资源,一般我们采用奇数个数,作为服务器部署个数。
8、主流应用场景
(1)配置管理
集中式的配置管理在应用集群中是非常常见的,一般商业公司内部都会实现一套集中的配置管理中心,应对不同的应用集群对于共享各自配置的需求,并且在配置变更时能够通知到集群中的每一个机器。
Zookeeper很容易实现这种集中式的配置管理,比如将APP1的所有配置配置到/APP1 znode下,APP1所有机器一启动就对/APP1这个节点进行监控(zk.exist("/APP1",true)),并且实现回调方法Watcher,那么在zookeeper上/APP1 znode节点下数据发生变化的时候,每个机器都会收到通知,Watcher方法将会被执行,那么应用再取下数据即可(zk.getData("/APP1",false,null));
以上这个例子只是简单的粗颗粒度配置监控,细颗粒度的数据可以进行分层级监控,这一切都是可以设计和控制的。
(2)集群管理
应用集群中,我们常常需要让每一个机器知道集群中(或依赖的其他某一个集群)哪些机器是活着的,并且在集群机器因为宕机,网络断链等原因能够不在人工介入的情况下迅速通知到每一个机器。