什么是ZooKeeper ZooKeeper由Yahoo开发,后来捐赠给了Apache,现已成为Apache顶级项目。ZooKeeper是一个开源的分布式应用程序协调服务器,其为分布式系统提供一致性服务。其一致性是通过类似Paxos算法的ZAB协议完成的。其主要功能包括:配置维护、分布式同步、集群管理、分布式事务等。
简单来说,ZooKeeper是一个分布式协调服务框架 。
Paxos算法 https://juejin.im/post/58285877d203090054f6126a
ZAB协议 作为一个优秀高效且可靠的分布式协调框架,ZooKeeper在解决分布式数据一致性问题时并没有直接使用Paxos,而是专门定制了一致性协议叫做ZAB(ZooKeeper Automic Broadcast)原子广播协议,该协议能够很好地支持崩溃恢复 。
ZAB中的三个角色 在介绍ZAB协议之前,我们首先来了解一下在ZAB中三个主要的角色,Leader、Follower、Observer。
Leader:集群中唯一的写请求处理者,能够发起投票(投票也是为了进行写请求)。 Follower:能够接收客户端的请求,如果是读请求则可以自己处理,如果是写请求则要转发给 Leader。在选举过程中会参与投票,有选举权和被选举权。 Observer:就是没有选举权和被选举权的Follower。 在ZAB协议中对zkServer(即上面我们说的三个角色的总称)还有两种模式的定义,分别是消息广播和崩溃恢复。
消息广播模式 第一步肯定需要Leader将写请求广播出去,让Leader问问Followers是否同意更新,如果超过半数以上的同意那么就进行Follower和Observer的更新(和Paxos一样): 这两个队列是用于ZAB的Follower和Observer保证顺序性。何为顺序性,比如我现在有一个写请求A,此时Leader将请求A广播出去,因为只需要半数同意就行,所以可能这个时候有一个FollowerF1因为网络原因没有收到,而Leader又广播了一个请求B,因为网络原因,F1竟然先收到了请求B然后才收到了请求A,这个时候请求处理的顺序不同就会导致数据的不同,从而产生数据不一致问题。
所以在Leader这端,它为每个其他的zkServer准备了一个队列,采用先进先出的方式发送消息。由于协议是通过TCP来进行网络通信的,保证了消息的发送顺序性,接受顺序性也得到了保证。
除此之外,在ZAB中还定义了一个全局单调递增的事务ID:ZXID,它是一个64位long型,其中高32位表示epoch年代,低32位表示事务id。epoch是会根据Leader的变化而变化的,当一个Leader挂了,新的Leader上位的时候,epoch就变了。而低32位可以简单理解为递增的事务id。
定义这个的原因也是为了顺序性,每个proposal在Leader中生成后需要通过其ZXID来进行排序,才能得到处理。
崩溃恢复模式 说到崩溃恢复我们首先要提到ZAB中的Leader选举算法,当系统出现崩溃影响最大应该是Leader的崩溃,因为我们只有一个Leader,所以当Leader出现问题的时候我们势必需要重新选举Leader。
Leader选举可以分为两个不同的阶段,第一个是我们提到的Leader宕机需要重新选举,第二则是当Zookeeper启动时需要进行系统的Leader初始化选举。下面我先来介绍一下ZAB是如何进行初始化选举的。
假设我们集群中有3台机器,那也就意味着我们需要两台以上同意(超过半数)。比如这个时候我们启动了server1,它会首先投票给自己,投票内容为服务器的myid和ZXID,因为初始化所以ZXID都为0,此时server1发出的投票为(1,0)。但此时server1的投票仅为1,所以不能作为Leader,此时还在选举阶段所以整个集群处于Looking状态。
接着server2启动了,它首先也会将投票选给自己(2,0),并将投票信息广播出去(server1也会,只是它那时没有其他的服务器了),server1在收到server2的投票信息后会将投票信息与自己的作比较。首先它会比较ZXID,ZXID大的优先为Leader,如果相同则比较myid,myid大的优先作为Leader。所以此时server1发现server2更适合做Leader,它就会将自己的投票信息更改为(2,0)然后再广播出去,之后server2收到之后发现和自己的一样无需做更改,并且自己的投票已经超过半数 ,则确定server2为Leader,server1也会将自己变为Follower。整个服务器就从Looking变为了正常状态。
当server3启动发现集群没有处于Looking状态时,它会直接以Follower的身份加入集群。
还是前面三个server的例子,如果在整个集群运行的过程中server2挂了,那么整个集群会如何重新选举Leader呢?其实和初始化选举差不多。
首先毫无疑问的是剩下的两个Follower会将自己的状态从Following变为Looking状态,然后每个server会向初始化投票一样首先给自己投票(这不过这里的zxid可能不是0了,这里为了方便随便取个数字)。
假设server1给自己投票为(1,99),然后广播给其他server,server3首先也会给自己投票(3,95),然后也广播给其他server。server1和server3此时会收到彼此的投票信息,和一开始选举一样,他们也会比较自己的投票和收到的投票(zxid大的优先,如果相同那么就myid大的优先)。这个时候 server1收到了server3的投票发现没自己的合适故不变,server3收到server1的投票结果后发现比自己的合适于是更改投票为(1,99)然后广播出去,最后server1收到了发现自己的投票已经超过半数就把自己设为Leader,server3也随之变为Follower。
那么说完了ZAB中的Leader选举方式之后我们再来了解一下崩溃恢复是什么。其实主要就是当集群中有机器挂了,我们整个集群如何保证数据一致性?
如果只是Follower挂了,而且挂的没超过半数的时候,因为我们一开始讲了在Leader中会维护队列,所以不用担心后面的数据没接收到导致数据不一致性。
如果Leader挂了那就麻烦了,我们肯定需要先暂停服务变为Looking状态然后进行Leader的重新选举(上面我讲过了),但这个就要分为两种情况了,分别是确保已经被Leader提交的提案最终能够被所有的Follower提交和跳过那些已经被丢弃的提案。
确保已经被Leader提交的提案最终能够被所有的Follower提交是什么意思呢?
假设Leader(server2) 发送commit请求,他发送给了server3,然后要发给server1的时候突然挂了。这个时候重新选举的时候我们如果把server1作为Leader的话,那么肯定会产生数据不一致性,因为server3肯定会提交刚刚server2发送的commit请求的提案,而server1根本没收到所以会丢弃。
那怎么解决呢?
这个时候server1已经不可能成为Leader了,因为server1和server3进行投票选举的时候会比较ZXID,而此时server3的ZXID肯定比server1的大了。
那么跳过那些已经被丢弃的提案又是什么意思呢?
假设Leader(server2)此时同意了提案N1,自身提交了这个事务并且要发送给所有Follower要commit的请求,却在这个时候挂了,此时肯定要重新进行Leader的选举,比如说此时选server1为Leader(这无所谓)。但是过了一会,这个挂掉的Leader又重新恢复了,此时它肯定会作为Follower的身份进入集群中,需要注意的是刚刚server2已经同意提交了提案N1,但其他server并没有收到它的commit信息,所以其他server不可能再提交这个提案N1了,这样就会出现数据不一致性问题了,所以该提案N1最终需要被抛弃掉。
Zookeeper的几个理论知识 了解了ZAB协议还不够,它仅仅是Zookeeper内部实现的一种方式,而我们如何通过Zookeeper去做一些典型的应用场景呢?比如说集群管理,分布式锁,Master选举等等。
这就涉及到如何使用Zookeeper了,但在使用之前我们还需要掌握几个概念。比如Zookeeper的 数据模型、会话机制、ACL、Watcher机制等等。
数据模型 zookeeper数据存储结构与标准的Unix文件系统非常相似,都是在根节点下挂很多子节点(树型)。但是Zookeeper中没有文件系统中目录与文件的概念,而是使用了znode作为数据节点。znode是Zookeeper中的最小数据单元,每个znode上都可以保存数据,同时还可以挂载子节点,形成一个树形化命名空间。
每个znode都有自己所属的节点类型和节点状态。其中节点类型可以分为持久节点、持久顺序节点、临时节点和临时顺序节点。
持久节点:一旦创建就一直存在,直到将其删除。 持久顺序节点:一个父节点可以为其子节点维护一个创建的先后顺序,这个顺序体现在节点名称上,是节点名称后自动添加一个由10位数字组成的数字串,从0开始计数。 临时节点:临时节点的生命周期是与客户端会话绑定的,会话消失则节点消失。临时节点只能做叶子节点,不能创建子节点。 临时顺序节点:父节点可以创建一个维持了顺序的临时节点(和前面的持久顺序性节点一样)。 节点状态中包含了很多节点的属性比如czxid、mzxid等等,在Zookeeper中是使用Stat这个类来维护的。下面是一些属性。
czxid:Created ZXID,该数据节点被创建时的事务ID。 mzxid:Modified ZXID,节点最后一次被更新时的事务ID。 ctime:Created Time,该节点被创建的时间。 mtime: Modified Time,该节点最后一次被修改的时间。 version:节点的版本号。 cversion:子节点的版本号。 aversion:节点的ACL版本号。 ephemeralOwner:创建该节点的会话的sessionID,如果该节点为持久节点,该值为0。 dataLength:节点数据内容的长度。 numChildre:该节点的子节点个数,如果为临时节点为0。 pzxid:该节点子节点列表最后一次被修改时的事务ID,注意是子节点的 列表 ,不是内容。 会话 Zookeeper客户端和服务端是通过TCP长连接维持的会话机制,其实对于会话来说你可以理解为保持连接状态。...