zookeeper是我在使用dubbo的时候认识的一个中间件,它是一个分布式服务框架,当时候,我只知道dubbo通过它可以注册一些服务列表,然后通过服务提供者和服务消费者互相协助,完整一个分布式的服务应用。
那zookeeper究竟是一个什么东西呢?最近,我想认真的了解一下它到底是何方神圣。经过阅读网上的一些文章后以及官网的一些它的原理图。我开始有了一个大概的认知。
我就用我们项目案例来讲解一下对zookeeper的认知。首先,在以前一个web项目,是所有的服务都是写在一个项目里面的,当需要集群的时候,复制相同的项目代码到其他服务器上面,这样子的话代码重复,以及耦合等等各种问题,从而诞生了微服务-soa。
dubbo就是微服务技术。我们在controller层调用service层的时候,在以前,是直接都写在一个项目里面的。但现在需要把这两层分离,分为两个项目,web-server和web-service。相当于,controller依赖的一些service,调用的时候都需要通过网络传输来实现。那么 web-server怎么知道调用哪一个web-service项目呢?没错,就是通过dubbo这个框架。顺便说说,在一个项目里面,比如用户,权限,还有其他复杂业务都可以把这些分为一个一个的服务,web-servcie1,web-service2.....,这样,web-server1就可以调用web-service2的服务,代码就可以解耦,不需要重复代码了。
说了那么久,为啥还没说到zookeeper?好了,现在来说说zookeeper在这里的作用。我们把web-server,web-service这些项目都统称为client,每当web-server,web-service启动项目,首先都会去与zookeeper服务器进行连接。web-server是cosumer,web-service是provider,provider是把服务注册到zoo上面去了,那到底详细是怎么样实现的。provider项目里面会引用zookeeper-client的api 在zookeeper上面创建数据。zookeeper的数据形式其实是一个树,以/app,/app/n1文件路径为名的 树,每个节点叫znodo.
比如,web-service有一个服务叫userService,client会发送消息到zoo上面,在zoo上面创建节点, create /worker, 然后在这个节点上面挂userSerivice的数据,当然上面可以有ip,port之类的。consumer链接后,进行订阅,它可以对该节点进行监听watcher,当数据更新后,就会通知consumer了,consumer就会知道 对应userService的相关信息,dubbo底层可以通过netty进行 远程调用服务。
对于单一模式,就是非常简单了,一台zookeeper,其他的都是服务。但如果zookeeper挂掉了,那岂不是整个集群都有问题了?所以就必须zookeeper集群。简单来说就是 部署多台zookeeper,然后最好是基数台,他们会选举Leader,其他都是follower, 写操作都会先发送到leader,操作完后,进行同步数据,zoo用的是 zab协议。(下面是抄人的http://www.cnblogs.com/sunddenly/p/4138580.html\)
3.2.1 广播模式
广播模式类似一个简单的两阶段提交:Leader发起一个请求,收集选票,并且最终提交,图3.3演示了我们协议的消息流程。我们可以简化该两阶段提交协议,因为我们并没有"aborts"的情况。followers要么确认Leader的Propose,要么丢弃该Leader的Propose。没有"aborts"意味着,只要有指定数量的机器确认了该Propose,而不是等待所有机器的回应。
图 3.3 The flow of message with protocol
广播协议在所有的通讯过程中使用TCP的FIFO信道,通过使用该信道,使保持有序性变得非常的容易。通过FIFO信道,消息被有序的deliver。只要收到的消息一被处理,其顺序就会被保存下来。
Leader会广播已经被deliver的Proposal消息。在发出一个Proposal消息前,Leader会分配给Proposal一个单调递增的唯一id,称之为zxid。因为Zab保证了因果有序,所以递交的消息也会按照zxid进行排序。广播是把Proposal封装到消息当中,并添加到指向Follower的输出队列中,通过FIFO信道发送到 Follower。当Follower收到一个Proposal时,会将其写入到磁盘,可以的话进行批量写入。一旦被写入到磁盘媒介当中,Follower就会发送一个ACK给Leader。 当Leader收到了指定数量的ACK时,Leader将广播commit消息并在本地deliver该消息。当收到Leader发来commit消息时,Follower也会递交该消息。
需要注意的是, 该简化的两阶段提交自身并不能解决Leader故障,所以我们 添加恢复模式来解决Leader故障。
3.2.2 恢复模式
(1) 恢复阶段概述
正常工作时Zab协议会一直处于广播模式,直到Leader故障或失去了指定数量的Followers。为了保证进度,恢复过程中必须选举出一个新Leader,并且最终让所有的Server拥有一个正确的状态。对于Leader选举,需要一个能够成功高几率的保证存活的算法。Leader选举协议,不仅能够让一个Leader得知它是leader,并且有指定数量的Follower同意该决定。如果Leader选举阶段发生错误,那么Servers将不会取得进展。最终会发生超时,重新进行Leader选举。在我们的实现中,Leader选举有两种不同的实现方式。如果有指定数量的Server正常运行,快速选举的完成只需要几百毫秒。
(2)恢复阶段的保证
该恢复过程的复杂部分是在一个给定的时间内,提议冲突的绝对数量。最大数量冲突提议是一个可配置的选项,但是默认是1000。为了使该协议能够即使在Leader故障的情况下也能正常运作。我们需要做出两条具体的保证:
①我们绝不能遗忘已经被deliver的消息,若一条消息在一台机器上被deliver,那么该消息必须将在每台机器上deliver。
②我们必须丢弃已经被skip的消息。
(3) 保证示例
第一条:
若一条消息在一台机器上被deliver,那么该消息必须将在每台机器上deliver,即使那台机器故障了。例如,出现了这样一种情况:Leader发送了commit消息,但在该commit消息到达其他任何机器之前,Leader发生了故障。也就是说,只有Leader自己收到了commit消息。如图3.4中的C2。
图 3.4 The flow of message with protocol
图3.4是"第一条保证"(deliver消息不能忘记)的一个示例。在该图中Server1是一个Leader,我们用L1表示,Server2和Server3为Follower。首先Leader发起了两个Proposal,P1和P2,并将P1、P2发送给了Server1和Server2。然后Leader对P1发起了Commit即C1,之后又发起了一个Proposal即P3,再后来又对P2发起了commit即C2,就在此时我们的Leader挂了。那么这时候,P3和C2这两个消息只有Leader自己收到了。
因为Leader已经deliver了该C2消息,client能够在消息中看到该事务的结果。所以该事务必须能够在其他所有的Server中deliver,最终使得client看到了一个一致性的服务视图。
第二条:
一个被skip的消息,必须仍然需要被skip。例如,发生了这样一种情况:Leader发送了propose消息,但在该propose消息到达其他任何机器之前,Leader发生了故障。也就是说,只有Leader自己收到了propose消息。如图3.4中的P3所示。
在图3.4中没有任何一个server能够看到3号提议,所以在图3.5中当server 1恢复时他需要在系统恢复时丢弃三号提议P3。
图3.5
在图3.5是"第二条保证"(skip消息必须被丢弃)的一个示例。Server1挂掉以后,Server3被选举为Leader,我们用L2表示。L2中还有未被deliver的消息P1、P2,所以,L2在发出新提议P10000001、P10000002之前,L2先将P1、P2两个消息deliver。因此,L2先发出了两个commit消息C1、C2,之后L2才发出了新的提议P10000001和P10000002。
如果Server1 恢复之后再次成为了Leader,此时再次将P3在P10000001和P10000002之后deliver,那么将违背顺序性的保障。
(4) 保证的实现
如果Leader选举协议保证了新Leader在Quorum Server中具有最高的提议编号,即Zxid最高。那么新选举出来的leader将具有所有已deliver的消息。新选举出来的Leader,在提出一个新消息之前,首先要保证事务日志中的所有消息都由Quorum Follower已Propose并deliver。需要注意的是,我们可以让新Leader成为一个用最高zxid来处理事务的server,来作为一个优化。这样,作为新被选举出来的Leader,就不必去从一组Followers中找出包含最高zxid的Followers和获取丢失的事务。
① 第一条
所有的正确启动的Servers,将会成为Leader或者跟随一个Leader。Leader能够确保它的Followers看到所有的提议,并deliver所有已经deliver的消息。通过将新连接上的Follower所没有见过的所有PROPOSAL进行排队,并之后对该Proposals的COMMIT消息进行排队,直到最后一个COMMIT消息。在所有这样的消息已经排好队之后,Leader将会把Follower加入到广播列表,以便今后的提议和确认。这一条是为了保证一致性,因为如果一条消息P已经在旧Leader-Server1中deliver了,即使它刚刚将消息Pdeliver之后就挂了,但是当旧Leader-Server1重启恢复之后,我们的Client就可以从该Server中看到该消息Pdeliver的事务,所以为了保证每一个client都能看到一个一致性的视图,我们需要将该消息在每个Server上deliver。
② 第二条
skip已经Propose,但不能deliver的消息,处理起来也比较简单。在我们的实现中,Zxid是由64位数字组成的,低32位用作简单计数器。高32位是一个epoch。每当新Leader接管它时,将获取日志中Zxid最大的epoch,新LeaderZxid的epoch位设置为epoch+1,counter位设置0。用epoch来标记领导关系的改变,并要求Quorum Servers通过epoch来识别该leader,避免了多个Leader用同一个Zxid发布不同的提议。
这个方案的一个优点就是,我们可以skip一个失败的领导者的实例,从而加速并简化了恢复过程。如果一台宕机的Server重启,并带有未发布的Proposal,那么先前的未发布的所有提议将永不会被deliver。并且它不能够成为一个新leader,因为任何一种可能的 Quorum Servers ,都会有一个Server其Proposal 来自与一个新epoch因此它具有一个较高的zxid。当Server以Follower的身份连接,领导者检查自身最后提交的提议,该提议的epoch 为Follower的最新提议的epoch(也就是图3.5中新Leader-Server2中deliver的C2提议),并告诉Follower截断事务日志直到该epoch在新Leader中deliver的最后的Proposal即C2。在图3.5中,当旧Leader-Server1连接到了新leader-Server2,leader将告诉他从事务日志中清除3号提议P3,具体点就是清除P2之后的所有提议,因为P2之后的所有提议只有旧Leader-Server1知道,其他Server不知道。
(5) Paxos与Zab
① Paxos一致性
Paxos的一致性不能达到ZooKeeper的要求,我们可以下面一个例子。我们假设ZK集群由三台机器组成,Server1、Server2、Server3。Server1为Leader,他生成了三条Proposal,P1、P2、P3。但是在发送完P1之后,Server1就挂了。如下图3.6所示。
图 3.6 Server1为Leader
Server1挂掉之后,Server3被选举成为Leader,因为在Server3里只有一条Proposal—P1。所以,Server3在P1的基础之上又发出了一条新Proposal—P2',P2'的Zxid为02。如下图3.7所示。
图3.7 Server2成为Leader
Server2发送完P2'之后,它也挂了。此时Server1已经重启恢复,并再次成为了Leader。那么,Server1将发送还没有被deliver的Proposal—P2和P3。由于Follower-Server2中P2'的Zxid为02和Leader-Server1中P2的Zxid相等,所以P2会被拒绝。而P3,将会被Server2接受。如图3.8所示。
图3.8 Server1再次成为Leader
我们分析一下Follower-Server2中的Proposal,由于P2'将P2的内容覆盖了。所以导致,Server2中的Proposal-P3无法生效,因为他的父节点并不存在。
② Zab一致性
首先来分析一下,上面的示例中为什么不满足ZooKeeper需求。ZooKeeper是一个树形结构,很多操作都要先检查才能确定能不能执行,比如,在图3.8中Server2有三条Proposal。P1的事务是创建节点"/zk",P2'是创建节点"/c",而P3是创建节点"/a/b",由于"/a"还没建,创建"a/b"就搞不定了。那么,我们就能从此看出Paxos的一致性达不到ZooKeeper一致性的要求。
为了达到ZooKeeper所需要的一致性,ZooKeeper采用了Zab协议。Zab做了如下几条保证,来达到ZooKeeper要求的一致性。
(a) Zab要保证同一个leader的发起的事务要按顺序被apply,同时还要保证只有先前的leader的所有事务都被apply之后,新选的leader才能在发起事务。
(b) 一些已经Skip的消息,需要仍然被Skip。
我想对于第一条保证大家都能理解,它主要是为了保证每个Server的数据视图的一致性。我重点解释一下第二条,它是如何实现。为了能够实现,Skip已经被skip的消息。我们在Zxid中引入了epoch,如下图所示。每当Leader发生变换时,epoch位就加1,counter位置0。
图 3.9 Zxid
我们继续使用上面的例子,看一下他是如何实现Zab的第二条保证的。我们假设ZK集群由三台机器组成,Server1、Server2、Server3。Server1为Leader,他生成了三条Proposal,P1、P2、P3。但是在发送完P1之后,Server1就挂了。如下图3.10所示。
图 3.10 Server1为Leader
Server1挂掉之后,Server3被选举成为Leader,因为在Server3里只有一条Proposal—P1。所以,Server3在P1的基础之上又发出了一条新Proposal—P2',由于Leader发生了变换,epoch要加1,所以epoch由原来的0变成了1,而counter要置0。那么,P2'的Zxid为10。如下图3.11所示。
图 3.11 Server3为Leader
Server2发送完P2'之后,它也挂了。此时Server1已经重启恢复,并再次成为了Leader。那么,Server1将发送还没有被deliver的Proposal—P2和P3。由于Server2中P2'的Zxid为10,而Leader-Server1中P2和P3的Zxid分别为02和03,P2'的epoch位高于P2和P3。所以此时Leader-Server1的P2和P3都会被拒绝,那么我们Zab的第二条保证也就实现了。如图3.12所示。
图 3.12 Server1再次成为Leader
好像没啥了,第一次写文章,别怪我太烂,哈哈哈哈