Zookeeper

目录

1. 环境搭建

2. 概念

3. 集群角色

4. Znode

4.1 Znode类型

4.2 Znode数据结构

5. Watcher

6. ACL

7. 客户端原生API

7.1 CRUD操作

7.2 Watcher示例

7.3 ACL示例

8. ZkClient API

9. Curator API

10. Zookeeper实现分布式锁

11. zookeeper选举过程

12. Zookeeper客户端请求过程


1. 环境搭建

播客: HDFS(zookeeper、hadoop)环境搭建

2. 概念

1. Zookeeper的主要作用是为分布式系统提供协调服务, 包括但不限于: 分布式锁, 统一命名服务, 配置管理, 负载均衡, 主控服务器选举以及主从切换等

2. Zookeeper自身通常也以分布式形式存在。一个Zookeeper服务通常由多台服务器节点构成,只要其中超过一半的节点存活,Zookeeper即可正常对外提供服务,所以Zookeeper也暗含高可用的特性。客户端可以通过TCP协议连接至任意一个服务端节点请求Zookeeper集群提供服务,而集群内部如何通信以及如何保持分布式数据一致性等细节对客户端透明。如下图所示

3. Zookeeper是以高吞吐量为目标进行设计的,故而在读多写少的场合有非常好的性能表现

Zookeeper具有高吞吐特性的主要原因有以下几点:

1. Zookeeper集群的任意一个服务端节点都可以直接响应客户端的读请求(写请求会不一样些,下面会详谈),并且可以通过增加节点进行横向扩展。这是其吞吐量高的主要原因

2. Zookeeper将全量数据存储于内存中,从内存中读取数据不需要进行磁盘IO,速度要快得多。

3. Zookeeper放松了对分布式数据的强一致性要求,即不保证数据实时一致,允许分布式数据经过一个时间窗口达到最终一致,这也在一定程度上提高了其吞吐量。

而写请求,或者说事务请求,因为要进行不同服务器结点间状态的同步,一定程度上会影响其吞吐量。故而简单的增加Zookeeper的服务器节点数量,对其吞吐量的提升并不一定能起到正面效果。服务器节点增加,有利于提升读请求的吞吐量,但会延长服务器节点数据的同步时间,必须视具体情况在这两者之间取得一个平衡


3. 集群角色

在Zookeeper集群中,分别有Leader,Follower和Observer三种类型的服务器角色。

  • Leader
    Leader服务器在整个正常运行期间有且仅有一台,集群会通过选举的方式选举出Leader服务器,由它同统一处理集群的事务性请求以及集群内各服务器的调度。

  • Follower
    Follower的主要职责有以下几点:
    • 参与Leader选举投票
    • 参与事务请求Proposal的投票
    • 处理客户端非事务请求(读), 并转发事务请求(写)给Leader服务器。
  • Observer
    Observer是弱化版的Follower。其像Follower一样能够处理非事务也就是读请求,并转发事务请求给Leader服务器,但是其不参与任何形式的投票,不管是Leader选举投票还是事务请求Proposal的投票。引入这个角色主要是为了在不影响集群事务处理能力的前提下提升集群的非事务处理的吞吐量。


4. Znode

ZooKeeper操作和维护的为一个个数据节点,称为 znode,采用类似文件系统的层级树状结构进行管理。如果 znode 节点包含数据则存储为字节数组(byte array); Znode天然具有目录和文件两重属性: 即Znode既可以当做文件往里面写东西,又可以当做目录在下面挂载其他Znode

创建 znode 时需要指定节点类型; znode 共有 4 种类型, 分别为: 持久(无序)、临时(无序)、持久有序和临时有序

4.1 Znode类型

  1. 持久结点(PERSISTENT)

    最常见的Znode类型,一旦创建将在一直存在于服务端,除非客户端通过删除操作进行删除。持久结点下可以创建子结点。

  2. 持久顺序结点(PERSISTENT_SEQUENTIAL)

    在具有持久结点基本特性的基础上,会通过在结点路径后缀一串序号来区分多个子结点创建的先后顺序。这工作由Zookeeper服务端自动给我们做,只要在创建Znode时指定结点类型为该类型

  3. 临时结点(EPHEMERAL)

    临时结点的生命周期和客户端会话保持一致。客户端段会话存在的话临时结点也存在,客户端会话断开则临时结点会自动被服务端删除。临时结点下不能创建子结点。

  4. 临时顺序结点(EPHEMERAL_SEQUENTIAL)

    具有临时结点的基本特性,又有顺序性。

4.2 Znode数据结构

node结构主要由存储于其中的数据信息状态信息两部分构成, 通过get命令获取一个Znode结点的信息如下

第一行存储的是ZNode的数据信息, 从cZxid开始就是Znode的状态信息

Stat中记录了这个 ZNode 的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和 cversion(当前ZNode的ACL版本); 

每个Znode结点被创建时版本号都为0,每更新一次都会导致版本号加1,即使更新前后Znode存储的值没有变化版本号也会加1。version值可以形象的理解为Znode结点被更新的次数。Znode状态信息中的版本号信息,使得服务端可以对多个客户端对同一个Znode的更新操作做并发控制。整个过程和java中的CAS有点像,是一种乐观锁的并发控制策略,而version值起到了冲突检测的功能。客户端拿到Znode的version信息,并在更新时附上这个version信息,服务端在更新Znode时必须必须比较客户端的version和Znode的实际version,只有这两个version一致时才会进行修改

Session会话:

客户端来创建一个和zk服务端连接的句柄。连接状态:CONNECTING\CONNECTED\CLOSED

对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号,这个编号反应了所有事务操作的先后顺序,应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。 这个编号也叫做时间戳——zxid(Zookeeper Transaction Id)


5. Watcher

Watcher(事件监听器),是Zookeeper中的一个很重要的特性。Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是Zookeeper实现分布式协调服务的重要特性

注意: 所有注册的Watcher在Client中会加入到waitEvent队列中,只有得到事件通知并且非None类型的事件的时候才会丢弃,并且被GC回收,因为Watcher是一次性通知,所以在通常情况的使用是: 每次都New一个新的Watcher注册在服务端,此时如果Watcher过多则会导致OutOfMemoryError: Java heap space

6. ACL

ACL(Access Control List)

内置的 ACL schemes

world: 默认方式,相当于全世界都能访问
auth: 代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)
digest: 
即用户名:密码这种方式认证,这也是业务系统中最常用的
ip: 使用Ip地址认证

ACL支持权限:

CREATE: 能创建子节点
READ:能获取节点数据和列出其子节点
WRITE: 能设置节点数据
DELETE: 能删除子节点
ADMIN: 能设置权限

7. 客户端原生API

pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lic</groupId>
    <artifactId>zookeeper</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.9</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

7.1 CRUD操作

package com.teacher.zookeeper.znode;
import com.teacher.zookeeper.watcher.ZookeeperWatcher;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
public class ZookeeperCrud {
   private String connectString="192.168.2.100:2181,192.168.2.101:2181,192.168.2.102:2181";
   private ZooKeeper zookeeper;
   public ZookeeperCrud() {
      try {
         /**
          * 初始化zookeeper实例
          */
         zookeeper=new ZooKeeper(connectString,5000,new ZookeeperWatcher());
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
   /**
    * 创建持久节点
    */
   public String createPersistent(String path,String data){
      try {
         /**
          * 调用zookeeper的方法创建
          */
         return  zookeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
      } catch (KeeperException e) {
         e.printStackTrace();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return  null;
   }
   /**
    * 创建临时节点
    */
   public String createEphemeral(String path,String data){
      try {
         /**
          * 调用zookeeper的方法创建
          */
         return  zookeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
      } catch (KeeperException e) {
         e.printStackTrace();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return  null;
   }
   /***
    * 更新信息
    */
   public String getData(String path) throws KeeperException, InterruptedException {
      byte data[] = zookeeper.getData(path,false,null);
      data = (data == null)? "null".getBytes() : data;
      return new String(data);
   }
   /***
    * 更新信息 
    */
   public Stat setData(String path, String data) throws KeeperException, InterruptedException {
      return zookeeper.setData(path, data.getBytes(), -1);
   }
   /***
    * 是否存在
    */
   public Stat exists(String path) throws KeeperException, InterruptedException {
      return zookeeper.exists(path,false);

   }
   /***
    * 删除
    */
   public void delete(String path) throws KeeperException, InterruptedException {
      zookeeper.delete(path,-1);
   }
   /***
    * 删除 递归
    */
   public void deleteRecursive(String path) throws KeeperException, InterruptedException {
      ZKUtil.deleteRecursive(zookeeper, path);
   }
   public void close() throws InterruptedException {
      zookeeper.close();
   }
}

测试:

package com.teacher.jiagouedu.znode;
import com.teacher.zookeeper.znode.ZookeeperCrud;
import org.apache.zookeeper.KeeperException;
public class Test {
   public static void main(String[] args) throws KeeperException, InterruptedException {
      ZookeeperCrud zookeeperCrud=new ZookeeperCrud();
      /**
       * 创建永久节点
       */
      zookeeperCrud.createPersistent("/lic_Pe","abc");
      /**
       * 删除节点
       */
     /*if(null!=zookeeperCrud.exists("/wukong")){
        zookeeperCrud.delete("/wukong");
     }*/
      /**
       * 创建临时节点
       */
      zookeeperCrud.createEphemeral("/lic_Ep","2020abc");
      /**
       * 获取数据
       */
      System.out.println(zookeeperCrud.getData("/lic_Pe"));
      System.out.println(zookeeperCrud.getData("/lic_Ep"));
   }
}

7.2 Watcher示例

package com.teacher.zookeeper.watcher;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

public class ZookeeperWatcher implements Watcher {
   private String connectString="192.168.2.100:2181,192.168.2.101:2181,192.168.2.102:2181";
   private ZooKeeper zookeeper;
   public ZookeeperWatcher() {
      try {
         /**
          * 创建zookeeper实例, 并且传入watcher
          */
         this.zookeeper = new ZooKeeper(connectString, 5000,this);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   /***
    * 创建持久节点
    */
   public String createPersistent(String path,String data){
      try {
         return  zookeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
      } catch (KeeperException e) {
         e.printStackTrace();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return  null;
   }
   /***
    * 创建临时节点
    */
   public String createEphemeral(String path,String data){
      try {
         return  zookeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
      } catch (KeeperException e) {
         e.printStackTrace();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return  null;
   }
   /***
    * 更新信息
    */
   public String getData(String path,boolean watcher) throws KeeperException, InterruptedException {
      byte data[] = zookeeper.getData(path,watcher,null);
      data = (data == null)? "null".getBytes() : data;
      return new String(data);
   }
   /***
    * 更新信息
    */
   public Stat setData(String path, String data) throws KeeperException, InterruptedException {
      return zookeeper.setData(path, data.getBytes(), -1);
   }
   /***
    * 是否存在
    */
   public Stat exists(String path,boolean watcher) throws KeeperException, InterruptedException {
      return zookeeper.exists(path,watcher);

   }
   /***
    * 删除
    */
   public void delete(String path) throws KeeperException, InterruptedException {
      zookeeper.delete(path,-1);
   }
   /***
    * 删除
    */
   public void deleteRecursive(String path) throws KeeperException, InterruptedException {
      ZKUtil.deleteRecursive(zookeeper, path);
   }
   public void close() throws InterruptedException {
      zookeeper.close();
   }
   public void process(WatchedEvent event) {
      // 连接状态
      Event.KeeperState keeperState = event.getState();
      // 事件类型
      Event.EventType eventType = event.getType();
      // 受影响的path
      String path = event.getPath();
      System.out.println("Watcher: 连接状态:"+keeperState+",事件类型:"+eventType+",受影响的path:"+path);
   }
}

测试:

package com.teacher.jiagouedu.watcher;
import com.teacher.zookeeper.watcher.ZookeeperWatcher;
import org.apache.zookeeper.KeeperException;

public class Test {
   public static void main(String[] args) throws KeeperException, InterruptedException {
      ZookeeperWatcher zookeeperCrud=new ZookeeperWatcher();
      zookeeperCrud.createPersistent("/lic_Per","Hello World");
      System.out.println(zookeeperCrud.getData("/lic_Per", true));
      Thread.sleep(Long.MAX_VALUE);
   }
}

7.3 ACL示例

package com.teacher.zookeeper.acl;
import com.teacher.zookeeper.watcher.ZookeeperWatcher;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;

public class ZookeeperAcl {
   private String connectString="192.168.2.100:2181,192.168.2.101:2181,192.168.2.102:2181";
   private ZooKeeper zookeeper;
   /** 认证类型 */
   final static String scheme = "digest";
   final static String auth="111";//这个就是用户名

   /****
    * 区分下auth和非auth 需要登录
    */
   public ZookeeperAcl(boolean acl) {
      try {
         this.zookeeper = new ZooKeeper(connectString,5000,new ZookeeperWatcher());
         zookeeper.addAuthInfo(scheme,auth.getBytes());//这行代码
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   /***
    * 无权限认证的
    * @param
    */
   public ZookeeperAcl() {
      try {
         this.zookeeper =new ZooKeeper(connectString,5000,new ZookeeperWatcher());
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   /***
    * 创建持久节点
    */
   public String createPersistent(String path,String data){
      try {
         return  zookeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
      } catch (KeeperException e) {
         e.printStackTrace();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return  null;
   }
   /***
    * 创建持久节点
    */
   public String createPersistentAcl(String path,String data){
      try {
         return  zookeeper.create(path, data.getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
      } catch (KeeperException e) {
         e.printStackTrace();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return  null;
   }
   /***
    * 创建临时节点
    */
   public String createEphemeral(String path,String data){
      try {
         return  zookeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
      } catch (KeeperException e) {
         e.printStackTrace();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return  null;
   }

   /***
    * 更新信息
    */
   public String getData(String path) throws KeeperException, InterruptedException {
      byte data[] = zookeeper.getData(path,false,null);
      data = (data == null)? "null".getBytes() : data;
      return new String(data);
   }
   /***
    * 更新信息
    */
   public Stat setData(String path, String data) throws KeeperException, InterruptedException {
      return zookeeper.setData(path, data.getBytes(), -1);
   }
   /***
    * 是否存在
    */
   public Stat exists(String path) throws KeeperException, InterruptedException {
      return zookeeper.exists(path,false);
   }
   /***
    * 删除
    */
   public void delete(String path) throws KeeperException, InterruptedException {
      zookeeper.delete(path,-1);
   }
   /***
    * 删除
    */
   public void deleteRecursive(String path) throws KeeperException, InterruptedException {
      ZKUtil.deleteRecursive(zookeeper, path);
   }
   public void close() throws InterruptedException {
      zookeeper.close();
   }

}

测试:

package com.teacher.jiagouedu.acl;
import com.teacher.zookeeper.acl.ZookeeperAcl;
import org.apache.zookeeper.KeeperException;

public class ZookeeperAclTest {

   public static void main(String[] args) throws KeeperException, InterruptedException {
      //无权限认证
      ZookeeperAcl zookeeperNoAcl = new ZookeeperAcl();
      zookeeperNoAcl.createPersistent("/licNOAcl","2020 Hello World!");
      System.out.println(zookeeperNoAcl.getData("/licNOAcl"));

      //有权限认证
      ZookeeperAcl zookeeperAcl= new ZookeeperAcl(true);
      zookeeperAcl.createPersistentAcl("/licAcl","2020 Hello World!");
      System.out.println(zookeeperAcl.getData("/licAcl"));

      System.out.println("======客户端zookeeperAcl操作licAcl节点========");
      zookeeperAcl.setData("/licAcl","Hello Zookeeper!");
      System.out.println(zookeeperAcl.getData("/licAcl"));

      System.out.println("======客户端zookeeperNoAcl操作licAcl节点========");
      System.out.println(zookeeperNoAcl.getData("/licAcl"));

      zookeeperAcl.deleteRecursive("/licAcl");
      zookeeperAcl.deleteRecursive("/licNOAcl");
   }

}

8. ZkClient API

依赖:

<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

示例:

package com.teacher.zookeeper2.watcher;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.IZkStateListener;
import org.I0Itec.zkclient.ZkClient;
import org.apache.zookeeper.Watcher;
import java.util.List;
public class ZkClientWatcher<T> {
   ZkClient zkClient;
   private String connectString="192.168.2.100:2181,192.168.2.101:2181,192.168.2.102:2181";
   public ZkClientWatcher() {
      this.zkClient = new ZkClient(connectString,5000,5000,new MyZkSerializer());
   }
   public  T readData(String path){
      return zkClient.readData(path);

   }
   public List<String> getChildren(String path){
      return zkClient.getChildren(path);

   }
   public  void writeData(String path,Object object){
      zkClient.writeData(path,object);

   }
   public  void deleteRecursive(String path){
      zkClient.deleteRecursive(path);

   }
   /***
    *
    * @param path
    * @param data
    */
   public void createPersistent(String path,Object data){
      zkClient.createPersistent(path,data);
   }

   public void lister(String path){
      /**
       * 对父节点添加监听变化
       */
      zkClient.subscribeDataChanges(path, new IZkDataListener() {
         public void handleDataChange(String dataPath, Object data) throws Exception {
            System.out.printf("变更的节点为:%s,%s", dataPath,data );
         }
         public void handleDataDeleted(String dataPath) throws Exception {
            System.out.printf("删除的节点为:%s", dataPath );
         }
      });
      /**
       * 对父节点添加监听子节点变化
       */
      zkClient.subscribeChildChanges(path, new IZkChildListener() {
         public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
            System.out.println("parentPath: " + parentPath+",currentChilds:"+currentChilds);
         }
      });
      /**
       * 对父节点添加监听子节点变化
       */
      zkClient.subscribeStateChanges(new IZkStateListener() {
         public void handleStateChanged(Watcher.Event.KeeperState state) throws Exception {
            if(state== Watcher.Event.KeeperState.SyncConnected){
               //当我重新启动后start,监听触发
               System.out.println("连接成功");
            }else if(state== Watcher.Event.KeeperState.Disconnected){
               System.out.println("连接断开");//当我在服务端将zk服务stop时,监听触发
            }else
               System.out.println("其他状态"+state);
         }
         public void handleNewSession() throws Exception {
            System.out.println("重建session");
         }
         public void handleSessionEstablishmentError(Throwable error) throws Exception {
         }
      });
   }
}

9. Curator API

依赖:

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-client</artifactId>
    <version>4.0.0</version>
    <exclusions>
        <exclusion>
            <artifactId>slf4j-api</artifactId>
            <groupId>org.slf4j</groupId>
        </exclusion>
        <exclusion>
            <artifactId>zookeeper</artifactId>
            <groupId>org.apache.zookeeper</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.0.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>4.0.0</version>
</dependency>

示例:

package com.teacher.zookeeper3.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
public class CuratorCrud {
   private final String connectString = "192.168.0.31:2181,192.168.0.32:2181,192.168.0.33:2181";
   CuratorFramework cf ;
   public CuratorCrud() {
      //1 重试策略:初试时间为1s 重试10次
      RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
      //2 通过工厂创建连接
      cf = CuratorFrameworkFactory.builder()
              .connectString(connectString)
              .sessionTimeoutMs(5000)
              .retryPolicy(retryPolicy)
//             .namespace("super")
              .build();
      //3 开启连接
      cf.start();
   }
   public String createPersistent(String path,String  data){
      try {
         cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path,data.getBytes());
      } catch (Exception e) {
         e.printStackTrace();
      }

      return  null;
   }
   public String getData(String path){
      try {
         return new String(cf.getData().forPath(path));
      } catch (Exception e) {
         e.printStackTrace();
      }
      return  null;
   }
   public void delete(String path){
      try {
         cf.delete().guaranteed().deletingChildrenIfNeeded().forPath(path);
      } catch (Exception e) {
         e.printStackTrace();
      }

   }
   public void setData(String path,String  data){
      try {
         cf.setData().forPath(path,data.getBytes());
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

10. Zookeeper实现分布式锁

思路一:

  1. 当一个客户端成功创建一个节点,另外一个客户端是无法创建同名的节点(达到互斥的效果)
  2. 注册该节点的监听事件,当节点删除,会通知其他的客户端,这个时候其他的客户端可以重新去创建该节点(可以认为是拿到锁的客户端释放锁,其他的客户端可以抢锁)
  3. 创建的节点应该时临时节点,这样保证我们在已经拿到锁的客户端挂掉了会自动释放锁

思路二(推荐):

  1. 每个客户端创建临时有序节点
  2. 客户端获取节点列表, 判断自己是否列表中的第一个节点, 如果是就获得锁如果不是就监听自己前面的节点等待前面节点被删除
  3. 如果获取锁就进行正常的业务流程,执行完释放锁

注意:

在步骤2中获取子节点列表与设置监听这两步操作的原子性问题,考虑这么个场景:客户端a对应子节点为/lock/lock-0000000000,客户端b对应子节点为/lock/lock-0000000001,客户端b获取子节点列表时发现自己不是序号最小的,但是在设置监听器前客户端a完成业务流程删除了子节点/lock/lock-0000000000,客户端b设置的监听器岂不是丢失了这个事件从而导致永远等待了?这个问题不存在的。因为zookeeper提供的API中设置监听器的操作与读操作是原子执行的,也就是说在读子节点列表时同时设置监听器,保证不会丢失事件; 如果设置失败可以根据业务逻辑进行处理, 比如, 可以认为前一个客户端已经释放锁, 让当前客户端获取锁或者进行一定次数的重试

最后,对于这个算法有个极大的优化点:假如当前有1000个节点在等待锁,如果获得锁的客户端释放锁时,这1000个客户端都会被唤醒,这种情况称为“羊群效应”;在这种羊群效应中,zookeeper需要通知1000个客户端,这会阻塞其他的操作,最好的情况应该只唤醒新的最小节点对应的客户端。应该怎么做呢?在设置事件监听时,每个客户端应该对刚好在它之前的子节点设置事件监听,例如子节点列表为/lock/lock-0000000000、/lock/lock-0000000001、/lock/lock-0000000002,序号为1的客户端监听序号为0的子节点删除消息,序号为2的监听序号为1的子节点删除消息

11. zookeeper选举过程

假设有5台服务器,编号分别是1, 2, 3, 4, 5

按1 -> 2 -> 3 -> 4 -> 5依次启动,选举过程如下:

服务器1启动: 给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于LOOKING
服务器2启动: 给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时总投票数没有大于半数,所以两个服务器的状态依然是LOOKING
服务器3启动: 给自己投票,同时与之前启动的服务器1, 2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为Leader,服务器1, 2成为Follower
服务器4启动: 给自己投票,同时与之前启动的服务器1, 2, 3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为Follower
服务器5启动: 后面的逻辑同服务器4成为Follower

按5 -> 4 -> 3 -> 2 -> 1依次启动,选举过程如下:

服务器5启动: 给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器5的状态一直属于LOOKING
服务器4启动: 给自己投票,同时与之前启动的服务器5交换结果,由于服务器5的编号大所以服务器5胜出,但此时总投票数没有大于半数,所以两个服务器的状态依然是LOOKING
服务器3启动: 给自己投票,同时与之前启动的服务器4, 5交换信息,由于服务器5的编号最大所以服务器5胜出,此时总投票数正好大于半数,所以服务器5成为Leader,服务器3, 4成为Follower
服务器2启动: 给自己投票,同时与之前启动的服务器5, 4, 3交换信息,服务器5的编号最大且之前服务器5已经胜出,所以服务器2成为Follower
服务器1启动: 后面的逻辑同服务器2成为Follower

12. Zookeeper客户端请求过程


1. Client端发送Request(封装成Packet)请求到Zookeeper
2. Zookeeper处理Request并将该请求放入Outgoing Queue (外出队列,就是让Zookeeper服务器处理的队列)
3. Zookeeper端处理Outgoing Queue,并将该事件移到Pending Queue中
4. Zookeeper端消费Pending Queue,并调用finishPacket(),生成Event
5. EventThread线程消费Event事件, 并且处理Watcher

 

参考播客: https://www.cnblogs.com/takumicx/p/9508706.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值