zookeeper能被各个牛逼的中间件项目中所依赖,已经说明了他的地位。一出手就是稳定的杀招。zookeeper是什么?官网中所说,zookeeper致力于开发和维护成为一个高度可靠的分布式协调器。
开局一张图,官网就是酷,(图片来自官网)。
zookeeper能干的事情很多:
【分布式锁】,zookeeper
特殊的数据结构和watcher机制,让他也能高效的实现分布式锁的功能,参考Curactor这款框架,分布式锁开箱即用。
【元数据管理】,Kafka
就是使用zookeeper存储核心元数据。
【分布式协调】,zookeeper
的watcher机制可以让分布式系统中的各个节点监听某个数据的变化,并且zookeeper可以把数据变化反向推送给订阅了的节点,例如kafka里面的各个broker和controller之间的协调。
【Master选举】,HDFS就是用了zookeeper来保证Namenode的 HA高可用,可以保证只有一台成为主。
zookeeper提供了全方位的分布式场景下丰富的功能,分布式协调的王者不是虚名。
zookeeper一般是集群部署,单机不可能满足上面提到的这些功能的实现。我们一般都是用3-5台机器。每台机器都会在内存存储所有的元数据。划重点,zookeeper是基于内存存储的,这已经决定了他能实现高吞吐高性能。
zookeeper的内存数据结构,就像linux系统的文件系统,是一种树形结构,每个节点我们叫做znode。zookeeper的节点大致分为三种类别,一个是临时节点,一个是持久节点,还有顺序节点。灵活运用,组合这些节点,我们在实际开发中能实现各种神奇的功能。
既然是分布式领域的一个王者,zookeeper具备哪些特性才让他立足呢?
要做到这些,架构设计上是绝对有很多亮点的,我们来看看zookeeper的架构设计。
首先我们先来认识一下zookeeper中的几个角色:
leader
,集群启动,一定会选择一个节点作为主节点。一个集群中只有一个主节点,并且只有主节点接收并处理写请求。follower
,从节点,首先从节点是不能处理写请求的,就算某个客户端连接到从节点提交写请求,最终也是转发到主节点leader处理。从节点只负责读请求。当leader宕机的时候,并且当前集群中宕机的数量不超过一半,那么集群会重新发起选举,从follower中选举出新的leader。Observer
,顾名思义,观察者,一般我们接触zookeeper貌似都不怎么用到它,也注意不到他,如果你希望你的读并发能力能抗更多的请求,你可以选择挂载Observer。它只提供数据读取的服务。当客户端和zookeeper建立起连接,是基于TCP的长连接,一个会话session
,通过心跳机制,感知是否断开,如果断开,只要在sessionTimeout
时间内重新连接,就能保持住长连接。
zookeeper的数据结构是znode树型结构,如上文一样,我们每次写入就是构建树节点下的叶子结点。并且创建的节点分为持久节点和临时节点。
znode的结构长什么样呢?我们执行一次get操作,可以得到如下这样子的数据结构。
上文说过zookeeper一个很重要的功能是分布式协调,这就要依赖于zookeeper的监听回调机制,watcher机制,当客户端监听一个节点,当节点发生变化的时候反向通知客户端,如此便可以实现对订阅数据变化的感知,并做出响应的处理。基于TCP,天然可以实现这样的功能。
Zookeeper一般都是集群部署,那么集群之间各个节点是如何同步数据的呢?如何才能保证数据对外的一致性呢?
这里就引出了Zookeeper的自己参照一致性协议paxos实现的,独有的ZAB协议,zookeeper Atomic Broadcast
,zookeeper原子广播协议。正是通过这个协议,zookeeper的集群之间进行数据同步,保证数据的强一致性。
首先我们来拍脑袋想一想哪些情况下数据会不一致呢?正如上文提到的,所有的写请求是转发给leader处理的,再加上同步给follower,这中间网络通信,会出现各种情况,甚至leader崩溃,follower崩溃,这些都会导致数据不一致。
【那么ZAB协议是怎么起到保证一致性的作用呢?】
Proposal
(提议,提案),并且将这个事务Proposal
发送给所有的Follower节点,也就是向所有的follower节点发送数据广播请求。Proposal
之后leader就要等待所有的Follower服务器的返回,(ack请求),划重点,在ZAB协议中明确了只要有超过半数的Follower节点正确的返回了ACK,就认为本次提案是successful的。那么此时leader就会向所有的Follower服务器广播commit消息,对前一次的事务Proposal发起提交。这里我们敏锐的可以感觉到,脑海里浮现了一个词语 2PC。这个后面再说。
我们再想想leader从哪里的呢?上面讨论的所有问题都是在唯一一台leader存在的情况下,那么leader是如何产生的?leader宕机了又该怎么办?这个是我们接下来要一点点分析的问题。
我们来讨论leader从哪里来?既然是最有权势的一个leader,没有规矩,谁都能当?那肯定集群里面的节点都争着要当的,这时候我们就要自然是要选举出来的。怎么选,这也是ZAB协议里有规定的。
当一个ZK集群启动的时候,会进入崩溃恢复模式,直到选举出一个leader,并且只要过半的Follower机器都和leader机器同步完数据,就会退出崩溃恢复模式,对外提供服务,此时就进入了消息广播模式。
崩溃恢复模式和消息广播模式是zookeeper集群工作的主要俩个模式。
我们接着说leader选举的事情,ZAB协议规定leader选举只要过半的机器都投票给某一台机器,这台机器就成为leader,这里自己也可以投票给自
己。如果leader突然宕机了,此时整个集群再次进入崩溃恢复模式,重新选举新的leader。
消息广播模式就是集群数据副本传递的主要的策略,上文我们也说到了2PC,但其实zookeeper还是为了性能做了优化,并不是要所有的follower都
返回ACK,只要过半数返回ACK就认为本次事务Proposal是写入成功的,就可以发起commit请求了。
这里我们其实是有疑惑的,这样子,zookeeper还是强一致性么?毕竟在某个时间点,也不是所有的机器都拥有一致的副本啊,这么看来也不能说
Zookeeper是实现了强一致性的CP模型,这里有个结论,最终所有的机器会实现数据副本一致性。
消息广播模式下,每次从客户端有一个事务请求过来,leader会转化为事务Proposal,并且写入本地磁盘,还会给每个事务Proposal分配一个全局
唯一的Zxid。
还有个细节,当leader向所有的Follower发起事务Proposal时,不是就随随便便发送的,leader为每一个Follower都维护了一个先进先出的队
列,每一个事务Proposal都是按照顺序依次放入,保证有序性。当Follower接收到事务Proposal时,会将其写入本地磁盘。
这里还有异步解耦的功效,也一定程度上提高了事务Proposal的广播速度。那么这里我们还可以说zookeeper实现的是顺序一致性,这样看起来,更大程度的保证了数据的一致性。
Zxid
其实就是事务id,为了保证事务的顺序一致性,zookeeper
采用了递增的事务id(Zxid
)来标识事务。所有的提案(proposal
)都在被提出的时候加上了 Zxid
。
Zxid
是64位的数字,高32位是任期编号,低32位是事务计数器:
epoch
,ZAB协议通过epoch
编号来区分 Leader
周期变化,用来标识 leader
关系是否改变,每次新 leader
被选出来,所有节点的新epoch值为:(epoch初始值=1):new epoch值=old epoch值+1可以理解成,标识当前集群属于哪个
leader
的统治时期,保证了即使旧leader
恢复后也没有人再从属于它(数据同步后就成了follower
),因为follower
只听从当前epoch的leader
的命令。
transactionCount
:用于递增计数,每接收到一条写入请求,这个值+1。新 leader选举后这个值重置为0。这样设计的好处在于老的
leader
挂了以后重启,它不会被选举为leader
,因此此时它的zxid
肯定小于当前新的leader
。当老的leader
作为follower
接入新的leader
后,新的leader
会让它将所有的拥有旧的epoch
号并且未被COMMIT
的proposal
清除掉。
以上大概描述了ZAB协议下,zk集群中leader和follower之间数据副本的同步过程,我们在这里稍微消耗脑细胞地想一下,有没有什么情况,会导致数据不一致?
Proposal
就宕机了,这时候会数据不一致么?【P1】Proposal
写入是成功的,并且广播给所有的Follower机器,也受到了过半机器的ACK,此时leader节点挂了,并且此时可能leader已经本地commit了,那么新的leader选举出来之后,这台老的leader重新启动了之后,数据会不一致么?【P2】细细一想,不经冷汗,这些也是问题啊,ZAB要保证自己说到的一致性就必须要解决这俩种情况,如何解决呢?
关键就在于新leader的选举算法,要保证2点:
上文所说的每个事务Proposal分配一个全局唯一的Zxid,并且是递增的。再加上任期的概念,一切又开始向着一致性的方向迈进了。
如果 leader
选举算法能够保证新选举出来的 leader
服务器拥有集群中所有机器最高编号(【Zxid最大】)的事务 Proposal
,那么就可以
保证这个新选举出来的 Leader
一定具有已经提交的提案。
毕竟所有提案被COMMIT
之前必须有超过半数的 follower ACK
,即必须有超过半数节点的服务器的事务日志上有该提案的 proposal
,因此,只要有合法数量的节点正常工作,就必然有一个节点保 存了所有被 COMMIT 消息的 proposal
状态。而这个节点肯定会拥有最大的Zxid
。
综上:
ZooKeeper
主要依赖ZAB
协议来实现分布式数据一致性,基于该协议,ZooKeeper
实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。