Zookeeper

Zookeeper

分布式协调框架,可扩展性,靠谱性,时序性。Zookeeper实际上是维护一个类似于文件系统的数据结构,通过操作Znode来进行数据的CURD,Znode总共有四个类型:
Zookeeper

  • PERSISTENT-持久化目录节点:客户端与zookeeper断开连接后,该节点依旧存在
  • PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点:客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
  • EPHEMERAL-临时目录节点:客户端与zookeeper断开连接后,该节点被删除
  • EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点:客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

Paxos

分布式选举算法,多个节点之间存在两种通讯模式:内存共享(Shared Memory),消息传递(Messages passing)而Paxos是通过消息传递来实现节点之间的通讯。
在Paxos中共分为三种角色:
Proposer:发起者,负责发起提案
Accepter:决策者,负责对Proposer发送的提案进行决策
Leaners:学习者,学习发起者最后的
为了避免单点故障或和算法的活锁问题(两个Proposer轮流交替的发起请求,Accepter会一直处于死循环问题),三种角色的都必须是集合形式,Proposer会产生一个主Proposer来单独负责发送请求。单个进程可以担任多种角色。

选举流程

Paxos选举Leader类似两段式提交,其中分为两个部分:

  • prepare阶段:
    • Proposer选取一个新提案编号N,并向半数以上的Accepter发送编号为N的Proposer请求。Proposer(N)
    • Accepter收到Proposer请求并判断请求中N来觉得进行生命操作
      • Accepter.Max(N)==null,Accepter返回{ok,null,null}允许接受提案;
      • Accepter.Max(N)<Proposer(N),Accepter记录请求N并返回{pok,null,null}
      • Accepter.Max(N)>Proposer(N),Accepter不响应或者拒绝该填,返回error1
  • accept阶段:
    • Proposer收到半数以上的Accepter请求,Proposer会发送一个针对[K,V]的提案请求给半数以上的Accepter;
    • Accepter接收到含有Value的Proposer请求,只要该Accepter中没有对编号大于N的Porposer请求做出过相应它就接受该提案,如果小于Accepter中最大的编号N,那就拒绝该请求或者返回ERROR并进入到阶段一中的重新增加编号N发送Accepter请求。
      Paxos两段式流程
实际选举理解

问题背景:假设我们有下图的系统,想要在server1,server2,server3选一个master。
paxos选举实例

prepare阶段

1. 每个server向proposer发送消息,表示自己要当leader,假设proposer收到消息的时间不一样,顺序是: proposer2 -> proposer1 -> proposer3,消息编号依次为1、2、3。
  紧接着,proposer将消息发给acceptor中超过半数的子成员(这里选择两个),如图所示,proposer2向acceptor2和acceptor3发送编号为1的消息,proposer1向acceptor1和accepto2发送编号为2的消息,proposer3向acceptor2和acceptor3发送编号为3的消息。
2. 假设这时proposer1发送的消息先到达acceptor1和acceptor2,它们都没有接收过请求,所以接收该请求并返回【pok,null,null】给proposer1,同时acceptor1和acceptor2承诺不再接受编号小于2的请求;
  紧接着,proposer2的消息到达acceptor2和acceptor3,acceptor3没有接受过请求,所以返回proposer2 【pok,null,null】,acceptor3并承诺不再接受编号小于1的消息。而acceptor2已经接受proposer1的请求并承诺不再接收编号小于2的请求,所以acceptor2拒绝proposer2的请求;
  最后,proposer3的消息到达acceptor2和acceptor3,它们都接受过提议,但编号3的消息大于acceptor2已接受的2和acceptor3已接受的1,所以他们都接受该提议,并返回proposer3 【pok,null,null】;
  此时,proposer2没有收到过半的回复,所以重新取得编号4,并发送给acceptor2和acceptor3,此时编号4大于它们已接受的提案编号3,所以接受该提案,并返回proposer2 【pok,null,null】。

accept阶段

1
  Proposer3收到半数以上(两个)的回复,并且返回的value为null,所以,proposer3提交了【3,server3】的提案。
  Proposer1也收到过半回复,返回的value为null,所以proposer1提交了【2,server1】的提案。
  Proposer2也收到过半回复,返回的value为null,所以proposer2提交了【4,server2】的提案。
(这里要注意,并不是所有的proposer都达到过半了才进行第二阶段,这里只是一种特殊情况)
2
  Acceptor1和acceptor2接收到proposer1的提案【2,server1】,acceptor1通过该请求,acceptor2承诺不再接受编号小于4的提案,所以拒绝;
  Acceptor2和acceptor3接收到proposer2的提案【4,server2】,都通过该提案;
  Acceptor2和acceptor3接收到proposer3的提案【3,server3】,它们都承诺不再接受编号小于4的提案,所以都拒绝。

所以proposer1和proposer3会再次进入第一阶段,但这时候 Acceptor2和acceptor3已经通过了提案(AcceptN = 4,AcceptV=server2),并达成了多数,所以proposer会递增提案编号,并最终改变其值为server2。最后所有的proposer都肯定会达成一致,这就迅速的达成了一致。

此时,过半的acceptor(acceptor2和acceptor3)都接受了提案【4,server2】,learner感知到提案的通过,learner开始学习提案,所以server2成为最终的leader。

Learner学习被选定的value(第二阶段accept的)

Learn

ZAB(Zookeeper Atomic Broadcast)

是Zookeeper一种支持崩溃恢复和原子广播协议,是Zookeeper保证数据一致性的核心算法。Zookeeper通过ZAB来保证分布式事务的最终一致性。基于该协议Zookeeper设置了一套主备模型(Leader和Follower模型)来确保集群中各个副本之间的数据一致性。该种模型下Zookeeper集群中会有一台专门负责写数据的节点,然后将数据同步到其他的Follower节点。ZK客户端在连接Zookeeper集群的时候是随机的连接到某一个节点当中,如果该请求是读请求当前节点就直接处理,如果是写请求则会将该请求发送给Leader节点,由Leader节点广播该事务直到过半节点写入成功再提交事务。

特性:

1.ZAB保证在Leader节点上已经提交的事务最终被所有服务器提交;
2.ZAB确保丢弃那些在Leader节点上已经被提出但是为提交的事务;

原理:

ZAB协议要求每个Leader节点都要经历三个阶段:发现,同步,广播

  • 发现:要求Zookeeper集群选举的出来的Leader进程都要维护一份客户端可用的Follower列表,将来客户端可以和这些Follower相互通信;
  • 同步:每个Leader都要做到将本身数据和Follower同步,做到多副本存储。这样符合CAP中的高可用和容错分区性质。同时Follower会将未处理完的请求消费完后写入本地的事务日志中;
  • 广播:Leader节点负责接收客户端新的事务Proposal请求,并将该请求广播到所有Follower节点中。

事务请求处理方式:

ZAB定义所有的事务请求(写请求)必须统一由Leader节点进行广播发送给剩余的Follower节点,Leader服务器将写请求转换成一个事务Porposal(或者数据复制请求)并分发到所有Follower节点。第一次分发Porposal后Leader节点会等待所有被分发Porposal的Follower节点返回ACK消息,只要Leader收到超过半数的ACK消息该节点就会向所有Follower节点发送Commit消息要求将上一个事务提交。
事务请求流转图

ZAB协议内容

ZAB协议切换过程

在Zookeeper集群启动中或者Leader节点重启,崩溃,网络中断时,ZAB协议会进入崩溃恢复模式,重新选举出新Leader。当新Leader选举出来后并且集群中已经有过半的Follower阶段已经完成数据同步后ZAB会退出崩溃恢复模式重新开始正常的消息广播。
如果在Zookeeper正常运行的过程中,有新节点加入集群中,那么新加入的节点自动进入崩溃恢复模式(找到Leader节点,同步数据),完成同步后该节点进入正常的消息广播。

ZAB切换模式条件:

1.Leader不能正常响应,例如:节点网络阻塞,节点崩溃,节点重启等;
2.Zookeeper集群中超过半数的节点无法正常的响应请求。

消息有序性

在ZAB消息广播中,Leader会将每一个事务请求转换成Proposal来进行广播,而Leader节点中会为每个Proposal申请一个唯一且递增的事务ID(Zxid),而Follower会按照顺序来执行消化每一个Proposal请求。

Zxid

ZAB中对于各个Proposel分发的64位数字构成的事务ID,其中存储的数据包含了两个部分Epoch、Index
Epoch:Zxid高32位构成的数字,代表当前集群所处的年代(每次Leader进行变更epoch都会增加一),当新Leader被选出来后该数值会被增加,所以当旧Leader恢复时即使发送了Proposel,其他Follower收到也不会进行处理,因为Follower只会服从epoch最高的Leader命令。
Index:Zxid低32位的数字,每次Leader产生新的Proposel时计数器都会加一来区分不同的Proposel。当有新Leader被重新选举出来后Index都会被重置。

消息广播模式

在Zookeeper集群中,数据副本的传递使用的就是消息广播模式,该模式类似两段式提交但是在判定是否可以提交时并不是等到所有节点都成功后才会返回而是有超过半数Follower响应ACK就开始提交阶段。
消息广播模式

消息广播流程
  • Leader节点将受到的写请求转换成Proposal提案,同时申请一个Zxid;
  • Leader节点会为每个Follower分配一个单独的队列,然后将要广播的Proposal依次放入队列中,队列使用FIFO策略;
  • Follower收到Proposal提案后首先将提案解析成事务日志写入磁盘,写入成功后向Leader返回ACK确认相应;
  • Leader节点收到超过半数的Follower节点ACK请求后认为该事务已经可以执行成功,自身先进行事务的提交,然后向所有Follower发送Commit消息;
  • Follower收到Commit事务后就会将上一条事务进行提交。
    ZAB协议核心就是只要有一个节点提交了事务,就要保证所有节点最终都会提交事务成功,而Leader节点中的消息队列以异步的方式向所有的Follower发送消息,防止消息的阻塞降低性能。

崩溃恢复模式

在Zookeeper集群奔溃恢复模式中,主要分为三个部分:Leader选举,数据恢复,开始消息广播,并且在崩溃中要做到数据的一致性。ZAB在崩溃恢复模式下要求各个节点满足两个要求:

  • 确保已经被Leader节点提交的Proposal一定会被其他的Follower节点提交;
  • 确保已经被Leader节点提出但是还未提交的Proposal一定会被其他Follower舍弃;
恢复模式流程

ZAB有三种类型的节点:

  • Leader:负责协调事务;
  • Follower:当前节点是跟随节点,服从Leader节点命令;
  • Election/Looking:节点处于选举状态,正在寻找Leader。

节点持久数据:

  • history:当前节点收到事务Prosonel的Log;
  • acceptEpoch:Follower已经接受Leader更改Epoch的new Epoch;
  • currentEpoch:当前Leader所处的年代;
  • lastZxid:当前节点收到最近的事务Proposel的Zxid;
恢复过程
1.选举阶段

Zookeeper集群规定所有节点必须在同一个轮次中进行选举,每个节点在开始新一轮投票的时候都会将本地Epoch(logicalClock )进行自增,但是该Epoch不会记录到Zxid中。在选举中只有节点得到了过半数的票数才会当选为准节点,只有在集群开始同步数据时该节点才会真的称为Leader节点。选举节点Zookeeper主要使用Fast Leader Election算法

  • 1.首先更新logicalclock并提议自己为leader并广播出去
  • 2.进入本轮投票的循环
  • 3.从recvqueue队列中获取一个投票信息,如果为空则检查是否要重发自己的投票或者重连,否则进入步骤4
  • 4.判断投票信息中的选举状态:
    • LOOKING状态:
      • 1.如果对方的logicalclock大于本地的logicalclock,则更新本地的logicalclock并清空本地投票信息统计箱recvset,并将自己作为候选和投票中的leader进行比较,选择大的作为新的投票,然后广播出去,否则进入步骤2
      • 2.如果对方的logicalclock小于本地的logicalclock,则忽略对方的投票,重新进入下一轮选举流程,否则进入步骤3
      • 3.如果两方的logicalclock相等,则比较当前本地被推选的leader和投票中的leader,选择大的作为新的投票,然后广播出去
      • 4.把对方的投票信息保存到本地投票统计箱recvset中,判断当前被选举的leader是否在投票中占了大多数(大于一半的server数量),如果是则需再等待finalizeWait时间(从recvqueue继续poll投票消息)看是否有人修改了leader的候选,如果有则再将该投票信息再放回recvqueue中并重新开始下一轮循环,否则确定角色,结束选举
    • OBSERVING状态:没有投票权,无视直接进入下一轮选举
    • FOLLOWING/LEADING:
      • 1.如果对方的logicalclock等于本地的logicalclock,把对方的投票信息保存到本地投票统计箱recvset中,判断对方的投票信息是否在recvset中占大多数并且确认自己确实为leader,如果是则确定角色,结束选举,否则进入步骤2
      • 2.将对方的投票信息放入本地统计不参与投票信息箱outofelection中,判断对方的投票信息是否在outofelection中占大多数并且确认自己确实为leader,如果是则更新logicalclock,并确定角色,结束选举,否则进入下一轮选举选举流程

Fast Leader Election(快速选举)
前面提到的 FLE 会选举拥有最新Proposal history (lastZxid最大)的节点作为 Leader,这样就省去了发现最新提议的步骤。这是基于拥有最新提议的节点也拥有最新的提交记录
成为 Leader 的条件:
1)选 epoch 最大的
2)若 epoch 相等,选 zxid 最大的
3)若 epoch 和 zxid 相等,选择 server_id 最大的(zoo.cfg中的myid)
节点在选举开始时,都默认投票给自己,当接收其他节点的选票时,会根据上面的 Leader条件 判断并且更改自己的选票,然后重新发送选票给其他节点。当有一个节点的得票超过半数,该节点会设置自己的状态为 Leading ,其他节点会设置自己的状态为 Following。

2.发现阶段

该阶段主要是让Follower大多数节点接收最新的Proposel,然后准Leader生成一个最新的Epoch让Follower接受做到集群内版本统一。最后选取一个拥有最大的Epoch的Follower作为初始化事务的集合。

  • 所有Follower将自己最新接收到的Proposel的Epoch发送给准Leader来计算最新Epoch(Fast Leader Election算法选举的话,完成后准Leader就拥有最大的Epoch);
  • Leader接受大部分的Follower的Epoch后,选举出来一个最大的Epoch,然后+1保存本地再发送给所有的Follower统一集群内版本;
  • Follower节点接收到最新的Epoch,检查本地Epoch和收到的NewEpoch:
    • LocalEpoch<=NewEpoch:更新LocalEpoch并向Leader发送ACK消息(ACK消息包括CurrentEpoch和处理过的Proposel结集合作为数据恢复用)
    • LocalEpoch>NewEpoch:该节点重新进入选举
  • Leader节点收到大部分的Follower节点ACK消息,从中选出Epoch最大的Follower事务结合作为集群所有节点初始化事务的集合(当Epoch想等后选取最大Zxid的Follower);
3.同步阶段

该阶段Leader节点将上阶段获取到集群中最新的事务集合发送给所有Follower节点,同步到集群中所有的副本。只有当超过半数的节点完成同步后,该节点才会成为真正的Leader节点

4.广播阶段

当数据全部同步完成后,集群开始正常工作可以对外开始提供服务,Leader可以开始广播事务。

Watch机制

Zookeeper的Znode当发生变化时(新增, 修改,删除),可以通过Watch机制来通知客户端。Watch是轻量级的,实际上就是JVM本地的CallBack,服务端时间上保存的是一个Watch的布尔值。服务端在处理节点请求的时候会根据客户端传递的Watch参数添加到对应的ZDatabase中进行持久化,通知自己NioServerCnxn作为一个Watch Callback监听服务端事件变化。Leader节点提交了某次修改的请求后通知对应的Follower,Follower根据自己Zdatabase中的信息 发送notifity给Zookeeper客户端。客户端收到Notify消息找到对应的变化的的Watch列表,进行出发回调。
Watch机制

特性
  • 1:一次性触发器
    客户端在Znode设置了Watch时,如果Znode内容发生改变,那么客户端就会获得Watch事件。例如:客户端设置getData("/znode1", true)后,如果/znode1发生改变或者删除,那么客户端就会得到一个/znode1的Watch事件,但是/znode1再次发生变化,那客户端是无法收到Watch事件的,除非客户端设置了新的Watch。
  • 2:发送至客户端
    Watch事件是异步发送到Client。Zookeeper可以保证客户端发送过去的更新顺序是有序的。例如:某个Znode没有设置watcher,那么客户端对这个Znode设置Watcher发送到集群之前,该客户端是感知不到该Znode任何的改变情况的。换个角度来解释:由于Watch有一次性触发的特点,所以在服务器端没有Watcher的情况下,Znode的任何变更就不会通知到客户端。不过,即使某个Znode设置了Watcher,且在Znode有变化的情况下通知到了客户端,但是在客户端接收到这个变化事件,但是还没有再次设置Watcher之前,如果其他客户端对该Znode做了修改,这种情况下,Znode第二次的变化客户端是无法收到通知的。这可能是由于网络延迟或者是其他因素导致,所以我们使用Zookeeper不能期望能够监控到节点每次的变化。Zookeeper只能保证最终的一致性,而无法保证强一致性。
  • 3:设置watch的数据内容
    Znode改变有很多种方式,例如:节点创建,节点删除,节点改变,子节点改变等等。Zookeeper维护了两个Watch列表,一个节点数据Watch列表,另一个是子节点Watch列表。getData()和exists()设置数据Watch,getChildren()设置子节点Watch。两者选其一,可以让我们根据不同的返回结果选择不同的Watch方式,getData()和exists()返回节点的内容,getChildren()返回子节点列表。因此,setData()触发内容Watch,create()触发当前节点的内容Watch或者是其父节点的子节点Watch。delete()同时触发父节点的子节点Watch和内容Watch,以及子节点的内容Watch。
Watch事件类型
  • EventType.NodeCreated 当node-x这个节点被创建时,该事件被触发
  • EventType.NodeChildrenChanged 当node-x这个节点的直接子节点被创建、被删除、子节点数据发生变更时,该事件被触发。
  • EventType.NodeDataChanged 当node-x这个节点的数据发生变更时,该事件被触发
  • EventType.NodeDeleted 当node-x这个节点被删除时,该事件被触发。
  • EventType.None 当zookeeper客户端的连接状态发生变更时,即KeeperState.Expired、KeeperState.Disconnected、KeeperState.SyncConnected、KeeperState.AuthFailed状态切换时,描述的事件类型为EventType.None
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值