深入浅出Zookeeper

Zookeeper

1.Zookeeper 的概述

  • Zookeeper是一个开源的分布式协调服务框架,主要用来解决分布式集群中应用系统的一致性问题和数据管理问题

2.Zookeeper的特点

  • Zookeeper本质是一个分布式文件系统,适合存放小文件,也可以理解为一个数据库

在这里插入图片描述

  • 在上图左侧, Zookeeper 中存储的其实是一个又一个 Znode, Znode 是 Zookeeper 中的节点
    • Znode 是有路径的, 例如 /data/host1, /data/host2, 这个路径也可以理解为是 Znode 的 Name
    • Znode 也可以携带数据, 例如说某个 Znode 的路径是 /data/host1, 其值是一个字符串 "192.168.0.1"
  • 正因为 Znode 的特性, 所以 Zookeeper 可以对外提供出一个类似于文件系统的视图, 可以通过操作文件系统的方式操作 Zookeeper
    • 使用路径获取 Znode

    • 获取 Znode 携带的数据

    • 修改 Znode 携带的数据

    • 删除 Znode

    • 添加 Znode

3.Zookeeper的应用场景

3.1 数据发布/订阅

  数据发布/订阅系统,需要发布者将数据发布到Zookeeper的节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。

  发布/订阅一般有两种模式:推模式拉模式,服务端主动将数据更新发送给所有订阅的客户端称为推模式,客户端主动请求获取最新数据称为拉模式

  Zookeeper采用推拉结合的模式.客户端向服务端注册自己需要关注的节点,一旦该节点数据发生变更,那么服务端就会向相应的客户端推送Watcher事件通知,客户端接收到此通知后,主动到服务端获取最新的数据

3.2 命名服务

  命名服务是分步式系统中较为常见的一类场景,分布式系统中,被命名的实体e通常可以是集群中的机器、提供的服务地址或远程对象等,通过命名服务,客户端可以根据指定名字来获取资源的实体,在分布式环境中,上层应用仅仅需要一个全局唯一的名字。Zookeeper可以实现一套分布式全局唯一ID的分配机制。
在这里插入图片描述

通过调用Zookeeper节点创建的API接口就可以创建一个顺序节点,并且在API返回值中会返回这个节点的完整名字,利用此特性,可以生成全局ID,其步骤如下

1. 客户端根据任务类型,在指定类型的任务下通过调用接口创建一个顺序节点,如"job-"。

2. 创建完成后,会返回一个完整的节点名,如"job-00000001"。

3. 客户端拼接type类型和返回值后,就可以作为全局唯一ID了,如"type2-job-00000001"。

3.3 分布式协调/通知

  Zookeeper中特有的Watcher注册与异步通知机制,能够很好地实现分布式环境下不同机器,甚至不同系统之间的协调与通知,从而实现对数据变更的实时处理。通常的做法是不同的客户端都对Zookeeper上的同一个数据节点进行Watcher注册,监听数据节点的变化(包括节点本身和子节点),若数据节点发生变化,那么所有订阅的客户端都能够接收到相应的Watcher通知,并作出相应处理。

  在绝大多数分布式系统中,系统机器间的通信无外乎心跳检测工作进度汇报系统调度

  ① 心跳检测,不同机器间需要检测到彼此是否在正常运行,可以使用Zookeeper实现机器间的心跳检测,基于其临时节点特性(临时节点的生存周期是客户端会话,客户端若宕机后,其临时节点自然不再存在),可以让不同机器都在Zookeeper的一个指定节点下创建临时子节点,不同的机器之间可以根据这个临时子节点来判断对应的客户端机器是否存活。通过Zookeeper可以大大减少系统耦合。
  ② 工作进度汇报,通常任务被分发到不同机器后,需要实时地将自己的任务执行进度汇报给分发系统,可以在Zookeeper上选择一个节点,每个任务客户端都在这个节点下面创建临时子节点,这样不仅可以判断机器是否存活,同时各个机器可以将自己的任务执行进度写到该临时节点中去,以便中心系统能够实时获取任务的执行进度。

  ③ 系统调度,Zookeeper能够实现如下系统调度模式:分布式系统由控制台和一些客户端系统两部分构成,控制台的职责就是需要将一些指令信息发送给所有的客户端,以控制他们进行相应的业务逻辑,后台管理人员在控制台上做一些操作,实际上就是修改Zookeeper上某些节点的数据,Zookeeper可以把数据变更以时间通知的形式发送给订阅客户端。

3.4分布式锁

  分布式锁用于控制分布式系统之间同步访问共享资源的一种方式,可以保证不同系统访问一个或一组资源时的一致性,主要分为排它锁和共享锁。

  排它锁又称为写锁或独占锁,若事务T1对数据对象O1加上了排它锁,那么在整个加锁期间,只允许事务T1对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作,直到T1释放了排它锁。

img

  ① 获取锁,在需要获取排它锁时,所有客户端通过调用接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。Zookeeper可以保证只有一个客户端能够创建成功,没有成功的客户端需要注册/exclusive_lock节点监听。
  ② 释放锁,当获取锁的客户端宕机或者正常完成业务逻辑都会导致临时节点的删除,此时,所有在/exclusive_lock节点上注册监听的客户端都会收到通知,可以重新发起分布式锁获取。

  共享锁又称为读锁,若事务T1对数据对象O1加上共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都被释放。在需要获取共享锁时,所有客户端都会到/shared_lock下面创建一个临时顺序节点

[外链图片转存失败(img-k9LujQrH-1565920888137)(assets/1558060430149.png)]

3.5 分布式队列

  有一些时候,多个团队需要共同完成一个任务,比如,A团队将Hadoop集群计算的结果交给B团队继续计算,B完成了自己任务再交给C团队继续做。这就有点像业务系统的工作流一样,一环一环地传下去.

  分布式环境下,我们同样需要一个类似单进程队列的组件,用来实现跨进程、跨主机、跨网络的数据共享和数据传递,这就是我们的分布式队列。

4.Zookeeper的架构

Zookeeper集群是一个基于主从架构的高可用集群
在这里插入图片描述

  每个服务器承担如下三种角色中的一种

  • Leader 一个Zookeeper集群同一时间只会有一个实际工作的Leader,它会发起并维护与各Follwer及Observer间的心跳。所有的写操作必须要通过Leader完成再由Leader将写操作广播给其它服务器。
  • Follower 一个Zookeeper集群可能同时存在多个Follower,它会响应Leader的心跳。Follower可直接处理并返回客户端的读请求,同时会将写请求转发给Leader处理,并且负责在Leader处理写请求时对请求进行投票。
  • Observer 角色与Follower类似,但是无投票权。

在这里插入图片描述

4.1 写请求流程

Zookeeper写操作流程图

  1. Client向Leader发送写请求

  2. Leader将写请求以Proposal的方式发给所有Follower并等待ACK

  3. Follower收到Leader的Proposal后返回ACK结果

  4. Leader得到过半数的ACK后(Leader对自己默认有一个ACK),向所有的Follower和Observer发送Commit

  5. Leader将处理结果返回给Client

    注意

    • Leader不需要得到Observer的ACK,因为Observer没有投票权
    • Leader不需要得到所有Follower的ACK,只要超过半数以上即可.且Leader对自己本身也有一个ACK.上图中有4个Follow,只需要其中两个返回ACK即可.因为(2+1)/(4+1)>1/2
    • Observer虽然没有投票权,但是仍需要同步Leader的数据,Observer和Follower除了无投票权,其他功能

5.Zookeeper的选举机制

5.1 术语介绍

myid
每个Zookeeper服务器,都需要在数据文件夹下创建一个名为myid的文件,该文件包含整个Zookeeper集群唯一的ID(整数)。例如某Zookeeper集群包含三台服务器,hostname分别为node01,node02,node03,其myid分别为1、2和3,则在配置文件中其ID与hostname必须一一对应,如下所示。在该配置文件zoo.cfg中,server.后面的数据即为myid

server.1=node01:2888:3888
server.2=node02:2888:3888
server.3=node03:2888:3888

zxid
类似于RDBMS中的事务ID,用于标识一次更新操作的Proposal ID.为了保证顺序性,该zkid必须单调递增。因此Zookeeper使用一个64位的数来表示,高32位是Leader的epoch,从1开始,每次选出新的Leader,epoch加一。低32位为该epoch内的序号,每次epoch变化,都将低32位的序号重置。这样保证了zkid的全局递增性。

选举算法
FastLeaderElection:该选举算法是标准的Fast Paxos算法实现,可解决LeaderElection选举算法收敛速度慢的问题。

服务器状态

  • Looking:该状态下的服务器认为当前集群中没有Leader,会发起Leader选举
  • Following:表明当前服务器角色是Follower,并且已经明确了Leader是谁
  • Leading:表明当前服务器角色是Leader,它会维护与Follower间的心
  • Observing:同Follower,区别是不参与选举,不参与集群写操作时的投票

选举参数信息
每个服务器在进行领导选举时,会发送如下关键信息

  • logicClock:表示这是该服务器发起的第多少轮投票
  • state:当前服务器的状态,如果Looking,Leading等
  • self_id:当前服务器的myid
  • self_zxid:当前服务器上所保存的数据的最大zxid
  • vote_id:被推举的服务器的myid
  • vote_zxid:被推举的服务器shang 所保存的数据的最大zxid

ZAB协议
为了保证写操作的一致性与可用性,Zookeeper专门设计了一种名为原子广播(ZAB)的支持崩溃恢复的一致性协议。基于该协议,Zookeeper实现了一种主从模式的系统架构来保持集群中各个副本之间的数据一致性。
根据ZAB协议,所有的写操作都必须通过Leader完成,Leader写入本地日志后再复制到所有的Follower节点。
一旦Leader节点无法工作,ZAB协议能够自动从Follower节点中重新选出一个合适的替代者,即新的Leader,该过程即为领导选举。该领导选举过程,是ZAB协议中最为重要和复杂的过程

5.2 投票流程

自增选举轮次
Zookeeper规定所有有效的投票都必须在同一轮次中。每个服务器在开始新一轮投票时,会先对自己维护的logicClock进行自增操作。

初始化选票
每个服务器在广播自己的选票前,会将自己的投票箱清空。该投票箱记录了所收到的选票。例:服务器2投票给服务器3,服务器3投票给服务器1,则服务器1的投票箱为(2, 3), (3, 1), (1, 1)。因为服务1会接收到服务器2,服务器3的投票情况(服务器2,3广播给服务器1的)

票箱中只会记录每一投票者的最后一票,如投票者更新自己的选票,则其它服务器收到该新选票后会在自己票箱中更新该服务器的选票。

发送初始化选票
最开始每个服务器都是通过广播把票投给自己的

接收外部投票
服务器会尝试从其它服务器获取投票,并记入自己的投票箱内。如果无法获取任何外部投票,则会确认自己是否与集群中其它服务器保持着有效连接。如果是,则再次发送自己的投票;如果否,则马上与之建立连接。

判断选举轮次
收到外部投票后,首先会根据投票信息中所宝航的logicClock来进行不同处理

  • 外部投票的logicClock大于自己的logiClock.说明该服务器的选举轮次低于其他服务器,立即清空自己的投票箱并将自己的logicClock更新为收到的logicClock,然后再对比自己之前的投票与收到的投票确定是否要更新自己的投票,最终再次将自己的投票广播出去
  • 外部投票的logicClock小于自己的logicClock,直接忽略这个投票信息,继续处理下一个投票
  • 外部投票的logicClock和自己相同,进行选票PK

选票PK
选票PK是基于(self_id, self_zxid)与(vote_id, vote_zxid)的对比

  • 外部投票的logicClock大于自己的logicClock,则将自己的logicClock及自己的选票的logicClock变更为收到的logicClock
  • 若logicClock一致,则对比二者的vote_zxid,若外部投票的vote_zxid比较大,则将自己的票中的vote_zxid与vote_myid更新为收到的票中的vote_zxid与vote_myid并广播出去;另外将收到的票及自己更新后的票放入自己的票箱。如果票箱内已存在(self_myid, self_zxid)相同的选票,则直接覆盖
  • 若二者vote_zxid一致,则比较二者的vote_myid,若外部投票的vote_myid比较大,则将自己的票中的vote_myid更新为收到的票中的vote_myid并广播出去,另外将收到的票及自己更新后的票放入自己的票箱

统计选票
如果已经确定有过半服务器认可了自己的投票(可能是更新后的投票),则终止投票。否则继续接收其它服务器的投票。

更新服务器状态
投票终止后,服务器开始更新自身状态。若过半的票投给了自己,则将自己的服务器状态更新为LEADING,否则将自己的状态更新为FOLLOWING

注:此处概念多取自作者Jason ,原文链接 http://www.jasongj.com/zookeeper/fastleaderelection/

5.3 选举的时机

Leader选举是保证分布式数据一致性的关键所在。当Zookeeper集群中的一台服务器出现以下两种情况之一时,需要进入Leader选举。

5.3.1 服务器启动时期的Leader选举

  若进行Leader选举,则至少需要两台机器,这里选取3台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器Server1启动时,其单独无法进行和完成Leader选举,当第二台服务器Server2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,于是进入Leader选举过程。选举过程如下

  (1) 每个Server发出一个投票。由于是初始情况,Server1和Server2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,此时Server1的投票为(1, 0),Server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。

  (2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。

  (3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下

    · 优先检查ZXID。ZXID比较大的服务器优先作为Leader。

    · 如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。

  对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较两者的ZXID,均为0,再比较myid,此时Server2的myid最大,于是更新自己的投票为(2, 0),然后重新投票,对于Server2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
  (4) 统计投票.每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1,Server2而言,都统计出集群中已经有两台机器接受了(2,0)的投票信息,此时便认为已经选出了Leader.

  (5) 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。

5.3.1.1 图解

背景:为更好演示,以三个服务器同时启动为例
初始投票都投给自己
在这里插入图片描述
在上图中,(1, 1, 0)第一位数代表投出该选票的服务器的logicClock,第二位数代表被推荐的服务器的myid,第三位代表被推荐的服务器的最大的zxid。由于该步骤中所有选票都投给自己,所以第二位的myid即是自己的myid,第三位的zxid即是自己的zxid。即**(logicClock,myid,zxid)**

在上图中,Server1下的(1,1)表示的是投票箱,指1号服务器投票给1号服务器.Server2或者Server3同理

更新选票
服务器接收外部服务器投票,进行选票的PK,根据规则更新自己的选票并广播出去,并将合适的选票存入自己的票箱,如图示:
在这里插入图片描述PK过程

  1. Server1接收到Server2的选票(1,2,0)和Server3的选票(1,3,0)后,首先比较logicClock,三台服务器都相同.然后比较zxid,也相同;最后比较myid,所以讲自己的选票更新为Server3的选票(1,3,0);
  2. 此时Server1将自己的票箱全部清空,即清除最初的(1,1),再将Server3的选票(3,3)和自己已经更新的选票(1,3)存入自己的票箱,即(1,3),(3,3)
  3. 再将自己更新后的选票广播出去
    同理,Server2也是这样的操作;Server3根据上述规则,不需要更新选票,所以仍然为(3,3)

确定Leader,更新状态
根据上述选票,三个服务器一致认为此时服务器3应该是Leader。因此服务器1和2都进入FOLLOWING状态,而服务器3进入LEADING状态。之后Leader发起并维护与Follower间的心跳。
在这里插入图片描述

5.3.2 服务器运行时期的Leader选举

在Zookeeper运行期间,Leader与非Leader服务器各司其职,即便当有非Leader服务器宕机或新加入,此时也不会影响Leader,但是一旦Leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮Leader选举,其过程和启动时期的Leader选举过程基本一致过程相同。

Follower发起新投票
Leader(服务器3)宕机后,Follower(服务器1和2)发现Leader不工作了,因此进入LOOKING状态并发起新的一轮投票,并且都将票投给自己。

广播更新选票
服务器1和2根据外部投票确定是否要更新自身的选票。

  • 如果服务器1和2的zxid相同。例如在服务器3宕机前服务器1与2完全与之同步。此时选票的更新主要取决于myid的大小
  • 如果服务器1和2的zxid不同。在旧Leader宕机之前,其所主导的写操作,只需过半服务器确认即可,而不需所有服务器确认。换句话说,服务器1和2可能一个与旧Leader同步(即zxid与之相同)另一个不同步(即zxid比之小)。此时选票的更新主要取决于谁的zxid较大

选出新Leader
经过上一步选票更新后,假设Server1的zxid更大,Server1Server2均将选票投给Server1,因此Server2成为Follower,而Server1成为新的Leader并维护与Server2的心跳。

旧Leader恢复后发起选举
旧的Leader恢复后,进入LOOKING状态并发起新一轮领导选举,并将选票投给自己。此时Server1会将自己的LEADING状态及选票(logicClock,myid,zxid)返回给Server3,而Server2将自己的FOLLOWING状态及选票(logicClock,myid,zxid)返回给Server3

旧Leader成为Follower
Server3了解到Leader为Server1,且根据选票了解到Server1确实得到过半服务器的选票,因此自己进入FOLLOWING状态。

6 一致性保证

ZAB协议保证了在Leader选举的过程中,已经被Commit的数据不会丢失,未被Commit的数据对客户端不可见

注:此处事例来源于作者Jason ,本人加入了少量自己的分析,原文链接 http://www.jasongj.com/zookeeper/fastleaderelection/

6.1 Commit过的数据不丢失

Failover前状态
为更好演示Leader Failover过程,本例中共使用5个Zookeeper服务器。A作为Leader,共收到P1、P2、P3三条消息,并且Commit了1和2,且总体顺序为P1、P2、C1、P3、C2。根据顺序性原则,其它Follower收到的消息的顺序肯定与之相同。其中B与A完全同步,C收到P1、P2、C1,D收到P1、P2,E收到P1,如下图所示
在这里插入图片描述

Failover:失效备援(为系统备援能力的一种,当系统中其中一项设备失效而无法运作时,另一项设备即可自动接手原失效系统所执行的工作)
P:指proposal
C:指commit
注意此示例的设计的规范之处:
·A没有C3,说明没有超过半数的服务器收到了P3,也就是说最多2台服务器收到了P3(包括Leader->A),所以示例中只有A和B收到了P3
·A已经C1,C2,说明已经超过半数的服务器收到了P1,P2,也就是说最少3台服务器收到了P1,P2,示例中遵循了这个规则

选出新Leader
假设旧Leader-A宕机,那么其他服务器就要根据上述的FastLeaderElection算法选举出新的Leader,即B,因为B的ZXID最大.此后C,D,E会主动将自己最大的ZXID发送给B,B比较自身ZXID和Follower的ZXID,将B已经Commit过的消息同步给Follower,以此保证各服务器的数据一致性.
在这里插入图片描述

  • P1和P2都被A Commit,因此B会通过同步保证P1、P2、C1与C2都存在于C、D和E中
  • P3由于未被A Commit,同时幸存的所有服务器中P3未存在于超过半数的数据服务器中,因此它不会被同步到其它Follower

通知Follower可对外服务
数据同步后,B会向D、C和E发送NewLeader命令并等待大多数服务器的ACK(下图中D和E已返回ACK,加上B自身,已经占集群的大多数,所以不用再等待C),然后向所有服务器广播Uptodate命令。收到该命令后的服务器即可对外提供服务。
在这里插入图片描述在此例中,P3未被A Commit过,同时因为没有过半的服务器收到P3,因此B也未Commit P3(如果有过半服务器收到P3,即使A未Commit P3,B会主动Commit P3,即C3),所以它不会将P3广播出去。

6.2 UnCommit过的数据对客户端不可见

对上述例子做一点改动,将服务器从5增加到7,服务器F包含P1、P2、C1、P3,服务器G包含P1、P2。此时服务器F、A和B都包含P3,但是因为票数未过半,因此B作为Leader不会Commit P3,而会通过TRUNC命令通知F删除P3。如图:

在这里插入图片描述
流程解析:

B在成为Leader后,先判断自身未Commit的消息(本例中即P3)是否存在于大多数服务器中从而决定是否要将其Commit。
然后B可得出自身所包含的被Commit过的消息中的最小zxid(记为min_zxid)与最大zxid(记为max_zxid)。C、D、E、F和G向B发送自身Commit过的最大消息zxid(记为max_zxid)以及未被Commit过的所有消息(记为zxid_set)。
B根据这些信息作出如下操作

  • 如果Follower的max_zxid与Leader的max_zxid相等,说明该Follower与Leader完全同步,无须同步任何数据
  • 如果Follower的max_zxid在Leader的(min_zxid,max_zxid)范围内,Leader会通过TRUNC命令通知Follower将其zxid_set中大于Follower的max_zxid(如果有)的所有消息全部删除

上述操作保证了未被Commit过的消息不会被Commit从而对外不可见。

综合上述案例解析:
P1  P2 C1  P3 C2 假设对应的ZXID为
1 2 3 4 5
1.Leader-B自身未C的消息(P3)不存在超过半数的服务器中,则P3不需要Commit .
2.得出B自身所包含的被Commit过的消息中的min_zxid(C1->3) , max_zxid(C2->5) .
3. Follower发送自身Commit过的最大zxid和UnCommitted的所有消息
 C->max_zxid(C1->3)     zxid_set(P2->2)
 D->max_zxid(0)     zxid_set(P1->1 P2->2)
 E->max_zxid(0)     zxid_set(P1->1)
 F->max_zxid(C1->3)     zxid_set(P2->2 P3->4)
 G->max_zxid(0)     zxid_set(P1->1 P2->2)
 可以直接看图中,黑色字体就是max_zxid,粉色字体就是zxid_set
4.判断是否有Follower的max_zxid与Leader的max_zxid相等的情况,无;
 判断是否有Follower的max_zxid在Leader的(min_zxid,max_zxid)范围内,有.
   C->通知Follower将其zxid_set中大于Follower的max_zxid(如果有)的所有消息全部删除
   F->通知Follower将其zxid_set中大于Follower的max_zxid(如果有)的所有消息全部删除,有 P3->4 ,4>3,通过TRUNC命令删除

7 总结

  • 由于使用主从复制模式,所有的写操作都要由Leader主导完成,而读操作可通过任意节点完成,因此Zookeeper读性能远好于写性能,更适合读多写少的场景
  • 虽然使用主从复制模式,同一时间只有一个Leader,但是Failover机制保证了集群不存在单点失败(SPOF)的问题
  • ZAB协议(Zookeeper Atomic Broadcast)保证了Failover过程中的数据一致性
  • 服务器收到数据后先写本地文件再进行处理,保证了数据的持久性
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值