Zookeeper使用及原理

Zookeeper简介

场景:

众所周知,分布式应用程序都需要一个协调服务。因为分布式应用程序是分布在多台主机上面的,分布在多台主机上面的应用要想共同地去很好地完成任务,当然得需要一个协调者,ZooKeeper就是这样一个协调者。

协调者不是这么好当的,对于分布式应用程序来说也是如此,协调服务很容易出现竞态条件、死锁等问题。为了减少分布式应用程序开发协调服务的成本,所以就诞生了ZooKeeper——开源的分布式协调服务。

提供了什么
  1. 文件系统
  2. 通知机制
快速

ZooKeeper中的数据是存在在内存中的,所以ZooKeeper可以实现高吞吐量和低延迟。

保证一致性

因为对于客户端来说,整个Zookeeper管理的服务器集群就是一台机器而已,因为无论客户端是访问哪台机器,获取的数据、进行的操作应该都是一样的。这就需要所有服务器的数据保持一致

ZooKeeper集群中的server是互相复制的,所以集群中的每个server数据都是一样的,正因如此,我们可以通过任意server来读取数据。

集群中的server必须能够互相感知,它们拥有相同的状态。客户端可以连接任意server,然后与server保持着TCP连接,通过此连接可以发送请求、获得响应,当此连接断掉的时候,客户端会重新连接一个不同的server。

集群中每一个server都可以服务客户端,读请求通过每个server的本地数据副本来提供写请求则由ZooKeeper协议来处理

作为协议的一部分,所有的写请求都会被转发到集群中一个叫做Leader的节点来处理,集群中的其余节点则称为Follower,Follower用来接收并处理来自Leader的提案。Leader宕机重新选举与Follower与Leader的同步也都是此协议的内容

Zookeeper服务端与客户端的通信协议

Zookeeper服务端与客户端的通信协议是基于TCP/IP协议自己封装的,也是TCP连接。

链接:https://www.jianshu.com/p/d01b1913cced

Zookeeper组成

zookeeper中的角色:

  1. 领导者(leader),负责进行投票的发起和决议,更新系统状态
  2. 学习者(learner),包括跟随者(follower)和观察者(observer)
  3. 跟随者(follower)用于接受客户端请求并想客户端返回结果,在选主过程中参与投票
  4. 观察者(observer)可以接受客户端连接,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展系统,提高读取速度
  5. 客户端(client),请求发起方,注意,这里的客户端就是我们要管理的服务器,包括数据库服务器、业务服务器等等。

在这里插入图片描述

具体情况如下:

在这里插入图片描述

Zookeeper读写

保证分布式一致性
  1. 顺序一致性:更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
  2. 原子性:数据更新原子性,一次数据更新要么成功,要么失败
  3. 单一视图:client无论连接到哪个server,数据视图都是一致的
  4. 可靠性:每个server保存一份数据副本
  5. 实时性(最终一致性):在一定事件范围内,client能读到最新数据
写流程
  1. 在Client向Follwer(也可以是observer)发出一个写的请求
  2. Follwer把请求发送给Leader
  3. Leader接收到以后开始发起投票并通知Follwer(这个只能是Follwer)进行投票
  4. Follwer把投票结果发送给Leader
  5. Leader将结果汇总后如果需要写入,则开始写入,然后commit,所有的learner都同步数据
  6. Follwer把请求结果返回给Client

在这里插入图片描述

Zookeeper文件系统

Zookeeper提供一个多层级的节点命名空间(节点称为znode)。与文件系统不同的是,文件系统中只有文件节点可以存放数据而目录节点不行,而Zookeeper命名空间提供的znode节点可以包含子znode,同时也可以包含数据。

也就是说znode 即是文件夹又是文件的概念,既能存储数据,也能创建子znode。

但是为了保证高吞吐和低延迟,Zookeeper在内存中维护了这个树状的目录结构,这种特性使得Zookeeper不能用于存放大量的数据,znode只适合存储非常小的数据,不能超过1M,最好都小于1K。

需要注意:Zookeeper的访问路径只有绝对路径,没有相对路径。

znode 有四种节点类型:

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

还有几点需要注意:

  1. 创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护。(无论创建的是否是有编号节点,都为顺序递增)。
  2. EPHEMERAL 临时类型的节点不能有子节点。
  3. 对于zk来说,有几个节点数据就会存储几份

原文链接:https://blog.csdn.net/qichangjian/article/details/88184705

Zookeeper主要角色的功能

Leader主要有三个功能:

  1. 恢复数据;
  2. 维持与Learner的心跳,接收Learner请求并判断Learner的请求消息类型;
  3. Learner的消息类型主要有PING消息、REQUEST消息、ACK消息、REVALIDATE消息,根据不同的消息类型,进行不同的处理。

Follower主要有四个功能:

  1. 向Leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);
  2. 接收Leader消息并进行处理;
  3. 接收Client的请求,如果为写请求,发送给Leader进行投票;
  4. 返回Client结果。

Follower的消息循环处理如下几种来自Leader的消息:

  1. PING消息: 心跳消息;
  2. PROPOSAL消息:Leader发起的提案,要求Follower投票;
  3. COMMIT消息:服务器端最新一次提案的信息;
  4. UPTODATE消息:表明同步完成;
  5. REVALIDATE消息:根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息;
  6. SYNC消息:返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。

为什么有observer

  1. Zookeeper需保证高可用和强一致性,为了支持更多的客户端,需要增加更多Server;
  2. Server增多,投票阶段延迟增大,影响性能,权衡伸缩性和高吞吐率,引入Observer,Observer不参与投票;
  3. 加入更多Observer节点,提高伸缩性,同时不影响吞吐率
  4. Observers接受客户端的连接,并将写请求转发给leader节点;

Zookeeper核心

1.聊天通道

因为要保证集群中所有的服务器之间相互关联,Zookeeper必须建立所有服务器之间的“聊天”通道,这条通道就是原子广播来维护的。

Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议

Zab协议有两种模式:它们分别是恢复模式(选主)和广播模式(同步)。

  1. 当服务启动或者在领导者崩溃后,ZAB 就进入了恢复模式,当领导者被选举出来,且大 多数 Server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 follower 之间具有相同的系统状态。
  2. 当 ZooKeeper 集群选举出 leader 同步完状态退出恢复模式之后,便进入了原子广播模式。 所有的写请求都被转发给 leader,再由 leader 将更新 proposal (提案)广播给 follower。

在这两种模式中,每个Server在工作过程中有三种状态:

  1. LOOKING:当前Server不知道leader是谁,正在搜寻
  2. LEADING:当前Server即为选举出来的leader
  3. FOLLOWING:leader已经选举出来,当前Server与之同步
  4. OBSERVING:观察者状态。表明当前服务器角色是Observer。
2.广播模式

在广播模式中:一旦leader已经和多数的follower进行了状态同步后,他就可以开始广播消息了,即进入广播状态。

广播模式需要保证proposal被按顺序处理,因此zk采用了递增的事务id号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64为的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch。低32位是个递增计数。

有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid。这是leader一台机器产生的,其他learner是不会产生的。

新server加入
这时候当一个server加入zookeeper服务中,它会在恢复模式下启动,发现leader,并和leader进行状态同步。待到同步结束,它也参与消息广播。

恢复模式的触发
Zookeeper服务一直维持在Broadcast状态,直到leader崩溃了或者leader失去了大部分的followers支持。

3.恢复模式(选举流程)

Zookeeper选举leader的流程如下:

假如集群中只有两个服务器,Server1和Server2。(当然一般来说个数应该是奇数,这里只是举例)

当集群启动时:

  1. 每个Server发出一个投票。由于是初始情况,Server1和Server2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,此时Server1的投票为(1, 0),Server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
  2. 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。
  3. 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则见下文。
  4. 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1、Server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了Leader。
  5. 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。

注意:过半数是要求大于半数,如果这里只有一台机器接受投票,刚好是半数,是不会认为已经选出leader。

投票PK规则:

  1. 优先检查ZXID。ZXID比较大的服务器优先作为Leader。(这个很重要:是数据最新原则,保证数据的完整性)。
  2. 如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。(集群的节点标识)

对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较两者的ZXID,均为0。再比较myid,此时Server2的myid最大,于是更新自己的投票为(2, 0),然后重新投票,对于Server2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。

当leader挂了时:

  1. 变更状态。Leader挂后,余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。
  2. 每个Server会发出一个投票。在运行期间,每个服务器上的ZXID可能不同,此时假定Server1的ZXID为123,Server3的ZXID为122;在第一轮投票中,Server1和Server3都会投自己,产生投票(1, 123),(3, 122),然后各自将投票发送给集群中所有机器。
  3. 接收来自各个服务器的投票。与启动时过程相同。
  4. 处理投票。与启动时过程相同,此时,Server1将会成为Leader。
  5. 统计投票。与启动时过程相同。
  6. 改变服务器的状态。与启动时过程相同。

为什要zxid大的服务器呢?

通常那台服务器上的数据越新,它的ZXID会越大,其成为Leader的可能性越大,也就越能够保证数据的恢复。如果ZXID相同,则myid(也可是SID)越大机会越大。

4.选举算法Fast Paxos算法的简单介绍

ZooKeeper 的选举算法有两种:一种是基于 Basic Paxos(Google Chubby 采用)实现的,另外 一种是基于 Fast Paxos(ZooKeeper 采用)算法实现的。系统默认的选举算法为 Fast Paxos。并且 ZooKeeper 在 3.4.0 版本后只保留了 FastLeaderElection 算法。

解释:Paxos算法是Zab协议中的一种核心算法。

当一台机器进入Leader选举时,当前集群可能会处于以下两种状态:

  1. 集群中已经存在Leader,此种情况一般都是某台机器启动得较晚,在其启动之前,集群已经在正常工作。对这种情况,该机器试图去选举Leader时,会被告知当前服务器的Leader信息,对于该机器而言,仅仅需要和Leader机器建立起连接,并进行状态同步即可。
  2. 集群中不存在Leader,这种情况比较复杂,需要选举。

假定Zookeeper由5台机器组成,SID分别为1、2、3、4、5,ZXID分别为9、9、9、8、8,并且此时SID为2的机器是Leader机器,某一时刻,1、2所在机器出现故障,因此集群开始进行Leader选举。在第一次投票时,每台机器都会将自己作为投票对象,于是SID为3、4、5的机器投票情况分别为(3, 9),(4, 8), (5, 8)。

选举步骤如下:

  1. 第一次投票:无论哪种导致进行Leader选举,集群的所有机器都处于试图选举出一个Leader的状态,即LOOKING状态,LOOKING机器会向所有其他机器发送消息,该消息称为投票。投票中包含了SID(服务器的唯一标识)和ZXID(事务ID),(SID, ZXID)形式来标识一次投票信息。
  2. 变更投票。每台机器发出投票后,也会收到其他机器的投票,每台机器会根据一定规则来处理收到的其他机器的投票,并以此来决定是否需要变更自己的投票,这个规则也是整个Leader选举算法的核心所在。这里大家都是选server3,算法规则见下。
  3. 确定Leader。经过第二轮投票后,集群中的每台机器都会再次接收到其他机器的投票,然后开始统计投票,如果一台机器收到了超过半数的相同投票,那么这个投票对应的SID机器即为Leader。此时Server3将成为Leader。

变更投票规则:

当收到其他服务器的投票时,需要对收到的投票的处理,也就是对其他服务器的投票(vote_sid, vote_zxid)和自己的投票(self_sid, self_zxid)对比的过程。
规则如下:

  1. 规则一:如果vote_zxid大于self_zxid,就认可当前收到的投票,并再次将该投票发送出去。
  2. 规则二:如果vote_zxid小于self_zxid,那么坚持自己的投票,不做任何变更。
  3. 规则三:如果vote_zxid等于self_zxid,那么就对比两者的SID,如果vote_sid大于self_sid,那么就认可当前收到的投票,并再次将该投票发送出去。
  4. 规则四:如果vote_zxid等于self_zxid,并且vote_sid小于self_sid,那么坚持自己的投票,不做任何变更。

根据规则,上面场景的结果为:
在这里插入图片描述

投票数据结构
每个投票中包含了两个最基本的信息,所推举服务器的SID和ZXID,投票(Vote)在Zookeeper中包含字段如下

  1. id:被推举的Leader的SID。
  2. zxid:被推举的Leader事务ID。
  3. electionEpoch:逻辑时钟,用来判断多个投票是否在同一轮选举周期中,该值在服务端是一个自增序列,每次进入新一轮的投票后,都会对该值进行加1操作。
  4. peerEpoch:被推举的Leader的epoch。
  5. state:当前服务器的状态。

转自:https://www.cnblogs.com/sweet6/p/10574574.html

为什么zookeeper集群的数目,一般为奇数个?

主要有两个原因:

  1. 容错,因为Paxos算法需要半数以上服务器存活,加上成本原因。
  2. 防脑裂,这是担心一个集群出现两个leader,这是主要原因。

容错
对比3台服务器的集群和4台服务器的集群:

  1. 3台服务器,至少2台正常运行才行(3的半数为1.5,半数以上最少为2),正常运行可以允许1台服务器挂掉
  2. 4台服务器,至少3台正常运行才行(4的半数为2,半数以上最少为3),正常运行可以允许1台服务器挂掉

明显4台服务器成本高于3台服务器成本,因而选3台。

防脑裂
一个zookeeper集群中,可以有多个follower、observer服务器,但是必需只能有一个leader服务器。

如果leader服务器挂掉了,剩下的服务器集群会通过半数以上投票选出一个新的leader服务器。

集群互不通讯情况:

  1. 一个集群3台服务器,全部运行正常,但是其中1台裂开了,和另外2台无法通讯。3台机器里面2台正常运行过半票可以选出一个leader。
  2. 一个集群4台服务器,全部运行正常,但是其中2台裂开了,和另外2台无法通讯。4台机器里面2台正常工作没有过半票以上达到3,无法选出leader正常运行。
  3. 一个集群5台服务器,全部运行正常,但是其中2台裂开了,和另外3台无法通讯。5台机器里面3台正常运行过半票可以选出一个leader。
  4. 一个集群6台服务器,全部运行正常,但是其中3台裂开了,和另外3台无法通讯。6台机器里面3台正常工作没有过半票以上达到4,无法选出leader正常运行。

如果只要一半就可以选出leader,对于4(6)台服务器的情况,可能导致裂开的2(3)台服务器选出自己的leader,未裂开的服务器也选出自己的leader,就有两个leader……

Zookeeper的发布订阅机制(Watcher)

原理简介

客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、节点删除、子目录节 点增加删除)时,zookeeper会通知客户端。

监听机制保证zookeeper保存的任何的数据的任何改变都能快速的相应到监听了该节点的应用程序。监听器的工作机制,其实是在客户端会专门创建一个监听线程,在本机的一个端口上等待zk集群发送过来的事件。

触发监听
  1. Znode的创建----nodeCreated
  2. Znode 被删除—nodeDelete
  3. Znode的数据变化—nodedatachanged
  4. Znode的子节点的变化----nodeChildrenchange
具体步骤

ZooKeeper 的 Watcher 机制主要包括:客户端线程、客户端 WatcherManager、Zookeeper 服务 器三部分。

  1. 客户端在向zookeeper服务器 注册的同时,会将Watcher对象存储在客户端的WatcherManager当中。
  2. 当zookeeper服务器触发watcher事件后,会向客户端发送通知。一次性触发 数据发生改变时,一个watcher event会被发送到client,但是client只会收到一次这样的信息。
  3. 客户端线程从 WatcherManager 中取出对应的 Watcher 对象来执行回调逻辑。Watcher通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容,需要客户端自己去调用。回调的过程是一个串行同步的过程。

原文链接:https://blog.csdn.net/qichangjian/article/details/88184705
在这里插入图片描述

原文链接:https://blog.csdn.net/qichangjian/article/details/88184705

Zookeeper的分布式锁(排它锁,用于写)

分布式锁场景

为什么需要分布式锁呢?考虑如下场景:
秒杀抢购时候,如何生成订单号(保证唯一),方案: UUid+时间戳方式。这在单台机器上是安全的,但是在集群情况下,是不能保证唯一性的。
对于全局变量count,单台可以加锁保证唯一性,集群情况下没法,除非是加分布式的锁。
在这里插入图片描述

分布式锁实现方案
  1. 数据库实现(效率低,不推荐)
  2. redis实现(使用redission实现,但是需要考虑思索,释放问题。繁琐一些)
  3. Zookeeper实现(使用临时节点,效率高,失效时间可以控制)
  4. Spring Cloud 实现全局锁(内置的)

这里我们介绍Zookeeper实现的分布式锁。

什么是分布式锁

分布式锁一般用在分布式系统或者多个应用中,用来控制同一任务是否执行或者任务的执行顺序。在项目中,部署了多个tomcat应用,在执行定时任务时就会遇到同一任务可能执行多次的情况,我们可以借助分布式锁,保证在同一时间只有一个tomcat应用执行了定时任务。

Zookeeper实现分布式锁原理

使用zookeeper创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推……

在这里插入图片描述
实现步骤:

  1. 多个Jvm同时在Zookeeper上创建同一个相同的节点( /Lock)
  2. zk节点唯一的! 不能重复!节点类型为临时节点, jvm1创建成功时候,jvm2和jvm3创建节点时候会报错,该节点已经存在。这时候 jvm2和jvm3进行等待。
  3. jvm1的程序现在执行完毕,执行释放锁。关闭当前会话。
  4. 临时节点不复存在了并且事件通知Watcher,jvm2和jvm3继续创建。
实现代码

代码如下:
父类

package com.toov5.Lock;

import org.I0Itec.zkclient.ZkClient;

//将重复代码抽象到子类中(模板方法设计模式)
public abstract class ZookeeperAbstractLock implements ExtLock {
    private static final String CONNECTION="192.168.91.5:2181";
    protected ZkClient zkClient = new ZkClient(CONNECTION);
    private String lockPath="/lockPath";
    
     //获取锁
      public void getLock() { 
          //1、连接zkClient 创建一个/lock的临时节点
          // 2、 如果节点创建成果,直接执行业务逻辑,如果节点创建失败,进行等待 
          if (tryLock()) {
            System.out.println("#####成功获取锁######");
        }else {
            //进行等待
            waitLock();
        }
     
        //3、使用事件通知监听该节点是否被删除    ,如果是,重新进入获取锁的资源  
        
    }
      
   //创建失败 进行等待
    abstract void waitLock();


    abstract boolean tryLock();
     
     
    //释放锁
      public void unLock() {
        //执行完毕 直接连接
          if (zkClient != null) {
            zkClient.close();
            System.out.println("######释放锁完毕######");
        }
        
    }
      
}

子类

package com.toov5.Lock;

import java.util.concurrent.CountDownLatch;

import org.I0Itec.zkclient.IZkDataListener;
public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {

    @Override
    boolean tryLock() {
        try {
            zkClient.createEphemeral(lockPath);
//            System.out.println("#########获取锁######");
            return true;
        } catch (Exception e) {
            // 如果失败 直接catch
            return false;
        }
    }

    @Override
    void waitLock() {

        IZkDataListener iZkDataListener = new IZkDataListener() {

            // 节点被删除
            public void handleDataDeleted(String arg0) throws Exception {
                if (countDownLatch != null) {
                    countDownLatch.countDown(); // 计数器为0的情况,await 后面的继续执行
                }
            }

            // 节点被修改
            public void handleDataChange(String arg0, Object arg1) throws Exception {

            }
        };

        // 监听事件通知
        zkClient.subscribeDataChanges(lockPath, iZkDataListener);
        // 控制程序的等待
        if (zkClient.exists(lockPath)) {  //如果 检查出 已经被创建了 就new 然后进行等待
            countDownLatch = new CountDownLatch(1);
            try {
                countDownLatch.wait(); //等待时候 就不往下走了   当为0 时候 后面的继续执行
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
        //后面代码继续执行
        //为了不影响程序的执行 建议删除该事件监听 监听完了就删除掉
        zkClient.unsubscribeDataChanges(lockPath, iZkDataListener);
    }
}

Zookeeper的分布式锁(共享锁,用于读)

共享锁,又称读锁。如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都被释放。

共享锁与排他锁的区别在于,加了排他锁之后,数据对象只对当前事务可见,而加了共享锁之后,数据对象对所有事务都可见。

定义锁:通过Zookeeper上的数据节点来表示一个锁,是一个类似于 /lockpath/[hostname]-请求类型-序号 的临时顺序节点

获取锁:客户端通过调用 create 方法创建表示锁的临时顺序节点,如果是读请求,则创建 /lockpath/[hostname]-R-序号 节点,如果是写请求则创建 /lockpath/[hostname]-W-序号 节点

判断读写顺序:大概分为4个步骤

  1. 创建完节点后,获取 /lockpath 节点下的所有子节点,并对该节点注册子节点变更的Watcher监听
  2. 确定自己的节点序号在所有子节点中的顺序
  3. 对于读请求:a. 如果没有比自己序号更小的子节点,或者比自己序号小的子节点都是读请求,那么表明自己已经成功获取到了共享锁,同时开始执行读取逻辑 b. 如果有比自己序号小的子节点有写请求,那么等待
  4. 对于写请求,如果自己不是序号最小的节点,那么等待
  5. 接收到Watcher通知后,重复步骤1

释放锁:与排他锁逻辑一致

基于Zookeeper实现共享锁流程:
在这里插入图片描述

但这样存在一个问题:羊群效应

在实现共享锁的 “判断读写顺序” 的第1个步骤是:创建完节点后,获取 /lockpath 节点下的所有子节点,并对该节点注册子节点变更的Watcher监听。这样的话,任何一次客户端移除共享锁之后,Zookeeper将会发送子节点变更的Watcher通知给所有机器,系统中将有大量的 “Watcher通知” 和 “子节点列表获取” 这个操作重复执行,然后所有节点再判断自己是否是序号最小的节点(写请求)或者判断比自己序号小的子节点是否都是读请求(读请求),从而继续等待下一次通知。

当集群规模比较大时,这些 “无用的” 操作不仅会对Zookeeper造成巨大的性能影响和网络冲击,更为严重的是,如果同一时间有多个客户端释放了共享锁,Zookeeper服务器就会在短时间内向其余客户端发送大量的事件通知–这就是所谓的 “羊群效应”。

解决羊群效应
这些重复操作很多都是 “无用的”,实际上每个锁竞争者只需要关注序号比自己小的那个节点是否存在即可

改进后的分布式锁实现:

  1. 客户端调用 create 方法创建一个类似于 /lockpath/[hostname]-请求类型-序号 的临时顺序节点
  2. 客户端调用 getChildren 方法获取所有已经创建的子节点列表(这里不注册任何Watcher)
  3. 如果无法获取任何共享锁,那么调用 exist 来对比自己小的那个节点注册Watcher
  4. 读请求:向比自己序号小的最后一个写请求节点注册Watcher监听
  5. 写请求:向比自己序号小的最后一个节点注册Watcher监听
  6. 等待Watcher监听,继续进入步骤2
    Zookeeper羊群效应改进前后Watcher监听图

链接:https://www.jianshu.com/p/a974eec257e6

Zookeeper恢复

zookeeper 在运行中会将数据加载在内存中,其不定期 会向磁盘存入快照。 在 zookeeper 重启后 ,会加载快照,从而完成数据的重新加载。

选举完成后,会进入Sysnchronization 同步阶段,把Leader 收集得到的最新历史事务日志,同步给集群中所有的Follower,只有当半数Follower同步成功,这个准Leader才能成为正式的Leader,故障恢复正式完成。

仍需了解的Zookeeper内容

  • 面试题总结https://www.cnblogs.com/lanqiu5ge/p/9405601.html

原文链接:https://blog.csdn.net/yanxilou/article/details/84400562

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值