zookeeper-常用命令,集成springboot,分布式锁实现和原理 ,dock集群zookeeper搭建,

(一)zookeeper数据模型

树形结构

  • 每个节点里面保存信息
  • 节点拥有子节点
  • 节点是临时的也可以是持久的
    在这里插入图片描述

四大节点
在这里插入图片描述

PERSISTENT-持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在

PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号

EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后,该节点被删除

EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

(二)zookeeper服务端常用命令并且 集成springboot

在这里插入图片描述

在这里插入图片描述
常用客户端命令

常用命令详情

docker 下操作 zoopkeeper

docker zookeeper 启动成功 ,简单操作zoopkeeper ,节点不能重复创建

[zk: localhost:2181(CONNECTED) 3] create /t1 ck
Created /t1
[zk: localhost:2181(CONNECTED) 4] create /t2 zk
Created /t2
[zk: localhost:2181(CONNECTED) 5] get /t1
ck
[zk: localhost:2181(CONNECTED) 6] set /t1 aaaa
[zk: localhost:2181(CONNECTED) 7] get /t1
aaaa
[zk: localhost:2181(CONNECTED) 8] delete /t1
[zk: localhost:2181(CONNECTED) 9] get /t1
Node does not exist: /t1
[zk: localhost:2181(CONNECTED) 10] create /t2 zk
Node already exists: /t2


#子节点的操作
[zk: localhost:2181(CONNECTED) 12] create /t1/y1 bbb
Created /t1/y1
[zk: localhost:2181(CONNECTED) 13] ls /t1 
[y1]
[zk: localhost:2181(CONNECTED) 14] delete /t1/y1
[zk: localhost:2181(CONNECTED) 15] delete /t1
[zk: localhost:2181(CONNECTED) 16] get /t1
Node does not exist: /t1

临时创建

// -e :创建:临时节点 
[zk: localhost:2181(CONNECTED) 0] create -e /app1 
Created /app1

// 退出客户端后 临时节点 app1 消失了
[zk: localhost:2181(CONNECTED) 4] quit 

[zk: localhost:2181(CONNECTED) 1] ls /
[t2, zookeeper]

创建顺序节点

[zk: localhost:2181(CONNECTED) 2] create -s /app1
Created /app10000000004
[zk: localhost:2181(CONNECTED) 3] create -s /app1
Created /app10000000005
[zk: localhost:2181(CONNECTED) 4] create -s /app1
Created /app10000000006
[zk: localhost:2181(CONNECTED) 5] create -s /app1
Created /app10000000007
[zk: localhost:2181(CONNECTED) 6] ls /
[app10000000004, app10000000005, app10000000006, app10000000007, t2, zookeeper]

创建临时顺序节点

Created /app20000000008
[zk: localhost:2181(CONNECTED) 8] create -es /app2
Created /app20000000009
[zk: localhost:2181(CONNECTED) 9] create -es /app2
Created /app20000000010
[zk: localhost:2181(CONNECTED) 10] ls /
[app10000000004, app10000000005, app10000000006, app10000000007, app20000000008, app20000000009, app20000000010, t2, zookeeper]
[zk: localhost:2181(CONNECTED) 11] quit

ls2 :命令

我是再2022年 docker 下安装的zookeeper ,没有 ls2 命令 !!!
用以下命令

ls -s /

[zk: localhost:2181(CONNECTED) 4] ls -s /
[app10000000004, app10000000005, app10000000006, app10000000007, t2, zookeeper]
cZxid = 0x0
ctime = Thu Jan 01 00:00:00 UTC 1970
mZxid = 0x0
mtime = Thu Jan 01 00:00:00 UTC 1970
pZxid = 0x17
cversion = 16
dataVersion = 0
aclVersion = 0
#是否临时
ephemeralOwner = 0x0
dataLength = 0
#有几个子节点
numChildren = 6

Curator

  • 高版本的可以操作低版本的zookeeper,反过来不行

导入坐标并且配置连接

     <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.2.0</version>
        </dependency>
        <!-- curator-recipes -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.2.0</version>
        </dependency>


在这里插入图片描述

重试策略

在这里插入图片描述
在这里插入图片描述
RetryUntil elapsed(v. (时间)消逝,流逝;) : 重试总共时间,到了结束不重试

创建客户端进行连接

   @Test
    void contextLoads() {
        //重试策略
        RetryPolicy retry = new ExponentialBackoffRetry(3000, 10);
        //创建连接
    /*   CuratorFramework client = CuratorFrameworkFactory.newClient("124.222.131.252:2181", 60000, 60000, retry);

        //开启连接
        client.start();
    */
            //namespace :起到隔离的作用  你 create /app1 ==  create /cunk/app1
        CuratorFramework client = CuratorFrameworkFactory.builder().connectString("124.222.131.252:2181")
                .retryPolicy(retry).namespace("cunk").build();

    }

前置知识 测试生命周期:

测试实例生命周期
为了允许隔离执行单个的测试方法,并避免由于可变测试实例状态而产生的意外副作用,JUnit在执行每个测试方法之前创建每个测试类的新实例(请参阅下面的讲解,何为测试方法)。这个”per-method”测试实例生命周期是JUnit Jupiter中的默认行为,类似于JUnit以前的所有版本。

如果您希望JUnit Jupiter在同一个测试实例上执行所有测试方法,只需使用@TestInstance(Lifecycle.PER_CLASS)对您的测试类进行注解即可。当使用这种模式时,每个测试类将创建一个新的测试实例。因此,如果您的测试方法依赖于存储在实例变量中的状态,则可能需要在@BeforeEach或@AfterEach方法中重置该状态。

“per-class”模式比默认的”per-method”模式有一些额外的好处。具体来说,使用”per-class”模式,可以在非静态方法和接口默认方法上声明@BeforeAll和@AfterAll(否则@BeforeAll与@AfterAll必须是注解在static的方法上才能生效)。因此,”per-class”模式也可以在@Nested测试类中使用@BeforeAll和@AfterAll方法。

如果使用Kotlin编程语言编写测试,则可能会发现,通过切换到”per-class”测试实例生命周期模式,可以更轻松地实现@BeforeAll和@AfterAll方法。
来源于: https://blog.csdn.net/HD243608836/article/details/101000429
推荐一个宝藏网站 : https://www.bookstack.cn/

对节点进行操作

节点类型枚举 
@Public
public enum CreateMode {
    PERSISTENT(0, false, false, false, false),
    PERSISTENT_SEQUENTIAL(2, false, true, false, false),
    EPHEMERAL(1, true, false, false, false),
    EPHEMERAL_SEQUENTIAL(3, true, true, false, false),
    CONTAINER(4, false, false, true, false),
    PERSISTENT_WITH_TTL(5, false, false, false, true),
    PERSISTENT_SEQUENTIAL_WITH_TTL(6, false, true, false, true);

创建一个节点

@SpringBootTest
class ZookeeperApplicationTests {

    CuratorFramework client ;

    //在任何test方法执行之气那执行
          //相对于after
    @BeforeEach
    void  contextLoads() {
        //重试策略
        RetryPolicy retry = new ExponentialBackoffRetry(3000, 10);
        //创建连接
    /*   CuratorFramework client = CuratorFrameworkFactory.newClient("124.222.131.252:2181", 60000, 60000, retry);

        //开启连接
        client.start();
    */
            //namespace :起到隔离的作用
        client  = CuratorFrameworkFactory.builder().connectString("***.***.***.***:2181")
                .retryPolicy(retry).namespace("cunk").build();
        client.start();
        //创建(create)节点 持久 临时 顺序 数据
        // 1.创建基本
    }


   @Test
   public void create() throws Exception {
       String path = client.create().forPath("/ck1");
       System.out.println(path);
   }
   
   res:
[zk: localhost:2181(CONNECTED) 7] ls /
[app10000000004, app10000000005, app10000000006, app10000000007, cunk, t2, zookeeper]
[zk: localhost:2181(CONNECTED) 8] ls /cunk
[ck1]
[zk: localhost:2181(CONNECTED) 9] get /cunk/ck1
***.***.***   === 默认是有数据 的 是当前机器的ip地址 


    @AfterEach
    public void close(){
        if (client!=null){
            client.close();
        }
    }
}

创建临时节点

在这里插入图片描述

 @Test
    public void createWithDateAndEPHEMERAL() throws Exception {

        //创建临时节点 ,当前会话一但结束 ,节点瞬间消失!!!
        /*
        *   临时节点消失策略 : 会话结束边消失
        *                   1.在linux 上调用客户端 然后退出客户端 ,节点消失
        *                   2.在java api上创建节点 ,然后关闭java客户端 ,连接终端 ,节点消失
        * */
        
        client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3","hi~".getBytes(StandardCharsets.UTF_8)) ;
    }

while(true){} :可以一直保持连接
然程序空转会一直保持连接
[zk: localhost:2181(CONNECTED) 12] ls /cunk
[app3, ck1]

假如我在linux 退出zk 会怎样呢 ? 临时节点还会存在吗?



WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0] ls /cunk
[app3, ck1]
^[[A[zk: localhost:2181(CONNECTED) 0] get /cunk/app3
hi~

可以发现 ,在linux 客户端启动 zk 然后退出 并不会 对 java api与zk 建立连接后创建的临时节点有任何影响,说明他们是两次不同的会话,不同会话产生的临时节点的消失 ,代表着 相对应会话的消失

此时关闭java客户端再查 app3

[zk: localhost:2181(CONNECTED) 4] get /cunk/app3
Node does not exist: /cunk/appc

创建多级节点

    //创建多级节点
    @Test
    public void createWithParent() throws Exception{
        //creatingParentContainersIfNeeded() :调用这个方法即使父节点不存在也可以创建多级节点
        client.create().creatingParentContainersIfNeeded().forPath("/app4/cunk1");
    }

查询节点

    1. get 查数据
  • 2.ls 子节点
  • 3.ls -s 查询状态信息
  • 在这里插入图片描述

    @Test
    public void checkWithParent() throws Exception {
        byte[] bytes = client.getData().forPath("/cunk2");
        System.out.println("========================================================");
        System.out.println("查询结果是: 》》"+new String(bytes)+" <<<<<");
    }

    @Test
    public void checkForParh() throws Exception {
        List<String> list = client.getChildren().forPath("/app4");
        System.out.println("========================================================");
        System.out.println("查询结果是: 》》"+list+" <<<<<");

    }

    @Test
    public void checkForParh3() throws Exception {
        //创建一个stat  查询出来的节点信息会封装进 stat
        Stat stat = new Stat();
        byte[] bytes = client.getData().storingStatIn(stat).forPath("/cunk2");
        System.out.println("========================================================");
        System.out.println("查询结果是: 》》"+stat+" <<<<<");
    }
========================================================
查询结果是: 》》59,59,1660367996068,1660367996068,0,0,0,0,10,0,59

更新节点

1.普通版
  @Test
    public void pathset() throws Exception {
        //修改app4 节点
        client.setData().forPath("/app4", "new data".getBytes(StandardCharsets.UTF_8));

        //再次查询app4节点的数值
        byte[] bytes = client.getData().forPath("/app4");
        System.out.println("========================================================");
        System.out.println("查询结果是: 》》"+new String(bytes)+" <<<<<");
    }
2.乐观锁版

前置知识 乐观锁,为什么更新需要带上版本号:乐观锁链接


  /*  当查询值为 1 时候 准备让值加 1 ,但是多线程情况下别的线程会对该值加一,总共会加 2
      修改数据一般需要带上版本号
      查询出来的版本号和 当前版本号相同才进行修改  (乐观锁)
  */
 @Test
    public void pathsetForVersion() throws Exception {
     //查询版本号
         Stat stat = new Stat();
        byte[] bytes = client.getData().storingStatIn(stat).forPath("/app4");

    //根据版本号就行修改
        int version = stat.getVersion();
      client.setData().withVersion(version).forPath("/app4","version Data".getBytes(StandardCharsets.UTF_8));

        //再次查询app4节点的数值
        System.out.println("========================================================");
        System.out.println("查询结果是: 》》"+new String(bytes)+" <<<<<");

    }
    
linux 查询数据:
[zk: localhost:2181(CONNECTED) 11] get /cunk/app4
versionDate

删除节点

```css
//删除节点
    // 1. 删除单个节点   2. 删除子节点的节点  3.必须删除成功  4. 回调
    @Test
    public void pathsetDelete() throws Exception {
        //删除单个节点
        client.delete().forPath("/app4") ;
    }

    //删除带有子节点的节点
    @Test
    public void pathsetDeletea() throws Exception {
        client.delete().deletingChildrenIfNeeded().forPath("/app4");
    }

    //必须删除成功 ,不断重试删除节点 。类似于rabbitmq 的消息发不出去的重试机制
    //在测试前可以关闭zookeeper,启动测试,过一段时间再连接发现还能删除。
    @Test
    public void mustpathsetDeletea() throws Exception {
        client.delete().guaranteed().forPath("/app4/cunk1") ;
    }


    //回调
    @Test
    public void returnDeletea() throws Exception {
     client.delete().guaranteed().inBackground(new BackgroundCallback() {
         //回调函数
         @Override
         public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
             System.out.println("删除成功!!!!!");
             System.out.println(curatorEvent);
         }
     }).forPath("/app4") ;

    }

res :
exiting
删除成功!!!!!
CuratorEventImpl{type=DELETE, resultCode=0, path='/app4', name='null', children=null, context=null, stat=null, data=null, watchedEvent=null, aclList=null, opResults=null}
2022-08-13 14:25:07.980  INFO 26036 --- [ain-EventThread] org.apache.zookeeper.ClientCnxn          : EventThread shut down for session: 0x1005b20cf740027
2022-08-13 14:25:07.980  INFO 26036 --- [           main] org.apache.zookeeper.ZooKeeper           : Session: 0x1005b20cf740027 closed

事件监听

类似于观察者模式+发布订阅 。 当一个节点信息改变 他会告诉其他的节点。
在这里插入图片描述
在这里插入图片描述

java api --事件监听

  • 监听某个节点
    @Test
    public void lensen() throws Exception {
        //创建 NodeCache            监听(/cunk/ck1)上的变动
        NodeCache nodeCache = new NodeCache(client, "/ck1");

        // 注册监听
        nodeCache.getListenable().addListener(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                System.out.println("节点变化了 ~~~");
                //获取修改后的节点数据
                byte[] data = nodeCache.getCurrentData().getData();
                System.out.println(new String(data));
            }
        });

        // 开启监听  buildInitial:true ,开启监听时候开启缓存
        nodeCache.start(true);

        //保证一直监听
        while (true){
        }
    }

  • 监听孩子节点
 @Test
    public void lensenC() throws Exception {
        //创建 NodeCache            监听(/cunk/ck1)上的变动
        PathChildrenCache pathChildrenCache = new PathChildrenCache(client, "/ck1", true);

        // 注册监听
     pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
         @Override
         public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
             System.out.println("儿子们变化了 ~~~");
             System.out.println(pathChildrenCacheEvent);
             // 监听孩子们 的数据变更
             PathChildrenCacheEvent.Type childrenCacheEventType = pathChildrenCacheEvent.getType();

             //判断孩子节点是不是更新
          if (childrenCacheEventType.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
              log.info("有个孩子节点更新咯 ~~");
              byte[] data = pathChildrenCacheEvent.getData().getData();
              System.out.println(new String(data));
          }
         }
     });

· 监听孩子们的事件类型
在这里插入图片描述

  • 监听自己和他的孩子们

  
    //监听孩子们节点
    @Test
    public void lensenAll() throws Exception {
        //创建 NodeCache            监听(/cunk/ck1)上的变动
        TreeCache treeCache = new TreeCache(client, "/ck1");

        // 注册监听
        treeCache.getListenable().addListener(new TreeCacheListener() {
            @Override
            public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
                log.info("节点变化了 ~~");
                log.info("变化情况是:{}",treeCacheEvent);
                TreeCacheEvent.Type type = treeCacheEvent.getType();
                if (type.equals(TreeCacheEvent.Type.NODE_ADDED)){
                    log.info("新增了个节点~~");
                }

            }
        });

        // 开启监听  buildInitial:true ,开启监听时候开启缓存
        treeCache.start();

        //保证一直监听
        while (true){

        }

    }

在这里插入图片描述

zook的分布式锁的实现(实现比理论简单)

在这里插入图片描述
要详细了解分布式锁 请移步:redis实现分布式锁…
在这里插入图片描述
分布式锁解决:跨机器进程之间的数据同步问题例如 抢票,秒杀。。。。

redis做分布式锁的弊端:虽然性能高 ,但是 redis挂了 ,所有人都能获得锁

zk概述

核心思想: 当客户想要获得锁,先创建节点,使用完毕锁,删除该节点

    1. 客户端获取锁时,再lock 节点下创建 临时顺序节点 临时:防止客户端宕机锁得不到释放,顺序:找最小节点

在这里插入图片描述
为什么创建的是临时顺序节点: 假如创建的是默认持久的节点 ,然后Clint1获取锁便 宕机了 ,Clien1与zk 失去连接 ,
但是/lock/1得不到释放。锁得不到释放!!!
这种情况再redis中的解决方案是:设置TTL 过期时间 。
临时节点的作用:当客户端 异常跟zk断联, 临时节点会自动释放 。其他节点就可以获得锁

    1. clin1 …3 在/lock 下分别对应的 /lock/1…3 谁的序号最小谁获得锁 故client1 获得锁
  • 3 锁的删除
    在这里插入图片描述

如果发现自己创建的节点不是lock所有节点序号中最小的(此时顺序节点的作用体现出来咯),
说明还没获得锁,此时客户端找到比自己小的
那个节点(找一个),同时对该节点注册监听,假如该节点删除,自己删除
在这里插入图片描述
lock1 删完 lock2 删 lock2 删完locak3 删

手写zk分布式可重入锁

public class ZkDistributedLock {

    private static final String ROOT_PATH = "/distributed";
    private static final ThreadLocal<Integer> THREAD_LOCAL = new ThreadLocal<>();

    private String path;

    private ZooKeeper zooKeeper;

    public ZkDistributedLock(ZooKeeper zooKeeper, String lockName){
        try {
            this.zooKeeper = zooKeeper;
            if (THREAD_LOCAL.get() == null || THREAD_LOCAL.get() == 0){
                this.path = zooKeeper.create(ROOT_PATH + "/" + lockName + "-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            }
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void lock(){
        Integer flag = THREAD_LOCAL.get();
        if (flag != null && flag > 0) {
            THREAD_LOCAL.set(flag + 1);
            return;
        }
        try {
            String preNode = getPreNode(path);
            // 如果该节点没有前一个节点,说明该节点时最小节点,放行执行业务逻辑
            if (StringUtils.isEmpty(preNode)){
                THREAD_LOCAL.set(1);
                return ;
            } else {
                CountDownLatch countDownLatch = new CountDownLatch(1);
                if (this.zooKeeper.exists(ROOT_PATH + "/" + preNode, new Watcher(){
                    @Override
                    public void process(WatchedEvent event) {
                        countDownLatch.countDown();
                    }
                }) == null) {
                    THREAD_LOCAL.set(1);
                    return;
                }
                // 阻塞。。。。
                countDownLatch.await();
                THREAD_LOCAL.set(1);
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
            // 重新检查。是否获取到锁
            try {
                Thread.sleep(200);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            lock();
        }
    }

    public void unlock(){
        try {
            THREAD_LOCAL.set(THREAD_LOCAL.get() - 1);
            if (THREAD_LOCAL.get() == 0) {
                this.zooKeeper.delete(path, 0);
                THREAD_LOCAL.remove();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取指定节点的前节点
     * @param path
     * @return
     */
    private String getPreNode(String path){

        try {
            // 获取当前节点的序列化号
            Long curSerial = Long.valueOf(StringUtils.substringAfterLast(path, "-"));
            // 获取根路径下的所有序列化子节点
            List<String> nodes = this.zooKeeper.getChildren(ROOT_PATH, false);

            // 判空
            if (CollectionUtils.isEmpty(nodes)){
                return null;
            }

            // 获取前一个节点
            Long flag = 0L;
            String preNode = null;
            for (String node : nodes) {
                // 获取每个节点的序列化号
                Long serial = Long.valueOf(StringUtils.substringAfterLast(node, "-"));
                if (serial < curSerial && serial > flag){
                    flag = serial;
                    preNode = node;
                }
            }

            return preNode;
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

java zk锁api(Curator)

这些概念请异步至 redis实现分布式锁…
Curator解决了很多zookeeper客户端非常底层的细节开发工作,包括连接重连、反复注册wathcer和NodeExistsException 异常等。
通过查看官方文档,可以发现Curator主要解决了三类问题:

  • 封装ZooKeeper client与ZooKeeper server之间的连接处理
  • 提供了一套Fluent风格的操作API
  • 提供ZooKeeper各种应用场景(recipe, 比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等)的抽象封装,这些实现都遵循了zk的最佳实践,并考虑了各种极端情况

Curator由一系列的模块构成,对于一般开发者而言,常用的是curator-framework和curator-recipes:

  • curator-framework:提供了常见的zk相关的底层操作
  • curator-recipes:提供了一些zk的典型使用场景的参考。本节重点关注的分布式锁就是该包提供的
    在这里插入图片描述

模拟卖票

创建买票客户端


@Slf4j
public class TickCLinet implements Runnable{
    private static int tick = 10 ; //10 张票
    private InterProcessMutex lock ;

    //创建锁
    public TickCLinet(InterProcessMutex lock ){
        this.lock = lock ;
    }


    @Override
    public void run( ) {
         while (true){
             //获取锁
             try {
                 // 获取失败等待3 秒
                 lock.acquire(3,TimeUnit.SECONDS);
                 if (tick > 0){
                   // log.info("线程:"+Thread.currentThread().getName()+"获得 了票 ");

                     this.wait();
                     tick -- ;
                     //log.info("当前票数:{}",tick);
                 }
             } catch (Exception e) {
                 e.printStackTrace();
             }finally {
                 try {
                     //防止程序发生异常锁得不到释放 加 finnaly 释放锁
                     lock.release();
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }

         }
    }
}

创建多个线程去获取票

@Slf4j
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
class TickTests  {
    private InterProcessMutex lock ;
    CuratorFramework client ;

    private int tickets = 10 ; //一共十张票

    //在任何test方法执行之气那执行
          //相对于after
    @BeforeEach
    void  contextLoads() {
        RetryPolicy retry = new ExponentialBackoffRetry(3000, 10);
        client  = CuratorFrameworkFactory.builder().connectString("124.222.131.252:2181")
                .retryPolicy(retry).namespace("cunk").build();
        client.start();
        //创建分布式锁
        lock = new InterProcessMutex(client, "/cunk");
    }

    @Test
    public void selltick() throws Exception{

        // 10个客户端去强票
        for (int i=0;i < 10;i++){
               new Thread(new TickCLinet(lock),"ClientApp_"+i).start();
          }

        //防止主线程先挂了
        while (true){

        }

    }


    @AfterEach
    public void testNodeclose(){
        if (client!=null){
            client.close();
        }
    }

}


[zk: localhost:2181(CONNECTED) 26] ls  /cunk/cunk
## 这是 zk自动创建的锁 
[_c_0581849d-005e-499c-a77f-6e246726da08-lock-0000000481, _c_0858fb78-c0fc-4e8d-9726-aab3ed5fe4d5-lock-0000000488, _c_0f1afac3-c590-405d-b663-eac5eabb1cf2-lock-0000000489, _c_45fe5ebc-ff86-4904-95a4-a3fd7f931950-lock-0000000484, _c_5fc2fd51-e9a8-4fa2-8a14-036d599a14dd-lock-0000000486, _c_a8089b0b-aff9-416d-8c5e-fa90378145b5-lock-0000000485, _c_d36115ef-da09-4a94-a36e-3f0503c248f4-lock-0000000482, _c_eeb5830a-cd9d-41f3-82cb-ab3bc7a267af-lock-0000000487, _c_f43096f8-ea9a-4fe6-8068-d11a0793e5b1-lock-0000000490, _c_f54c84c4-cabe-4b59-894d-8a6b424888d2-lock-0000000483]

# 当线程执行结束 或者异常关闭之后 锁得到释放 
[zk: localhost:2181(CONNECTED) 27] ls  /cunk/cunk
Node does not exist: /cunk/cunk

这个报错是正常现象(内部机制问题)…
在这里插入图片描述

有个小的知识点: 当程序异常结束的时候 , zk 释放锁不是瞬间释放的 ,是过了至少十秒才释放 ,意思是 一个服务宕机了 ,另外一个客户端不能瞬间获得锁 ,, 虽然这个研究有点无聊哈哈哈…
我有个问题 ,假如redis 和zk 做分布式锁 同时宕机了,redis由于watchdog 自动释放锁 , zk由于客户端断开连接 下一个获得锁的 谁快一些呢 ????

zookeeper集群搭建

领导选举

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

具体怎么搭建 :dockers下搭建 zk集群

  • 怎么创建网卡…可以看我docker专栏 ,说白了 十个zk集群需要在同一个网络下面
  • 需要一个投票选举接口
  • 停掉只剩一个zk ,集群会不可用,因为leader需要半数投票选举出来
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值