• 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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    临时创建

    // -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]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    创建顺序节点

    [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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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>
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    重试策略

    在这里插入图片描述
    在这里插入图片描述
    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();
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    前置知识 测试生命周期:

    测试实例生命周期
    为了允许隔离执行单个的测试方法,并避免由于可变测试实例状态而产生的意外副作用,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/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    对节点进行操作

    节点类型枚举 
    @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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    创建一个节点

    @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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    创建临时节点

    在这里插入图片描述

     @Test
        public void createWithDateAndEPHEMERAL() throws Exception {
    
            //创建临时节点 ,当前会话一但结束 ,节点瞬间消失!!!
            /*
            *   临时节点消失策略 : 会话结束边消失
            *                   1.在linux 上调用客户端 然后退出客户端 ,节点消失
            *                   2.在java api上创建节点 ,然后关闭java客户端 ,连接终端 ,节点消失
            * */
            
            client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3","hi~".getBytes(StandardCharsets.UTF_8)) ;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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~
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    此时关闭java客户端再查 app3

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

    创建多级节点

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

    查询节点

      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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    更新节点

    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)+" <<<<<");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    删除节点

    ```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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    事件监听

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

    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){
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 监听孩子节点
     @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));
              }
             }
         });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

    • 监听自己和他的孩子们
    
      
        //监听孩子们节点
        @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){
    
            }
    
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在这里插入图片描述

    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 删

    java zk锁api

    这些概念请异步至 redis实现分布式锁…

    在这里插入图片描述

    模拟卖票

    创建买票客户端

    
    @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();
                     }
                 }
    
             }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    创建多个线程去获取票

    @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();
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    
    [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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

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

    zookeeper集群搭建

    领导选举

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

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

    • 怎么创建网卡…可以看我docker专栏 ,说白了 十个zk集群需要在同一个网络下面
    • 需要一个投票选举接口
    • 停掉只剩一个zk ,集群会不可用,因为leader需要半数投票选举出来
  • 相关阅读:
    网鼎杯预赛2022密码
    Python基础(四):Python必需掌握基础注释、变量、输出
    Java数据结构第二课 —— 泛型(1)
    Nacos作为注册中心和配置中心的使用总结
    一个手机ip从这个城市去到另一个城市多久会变
    List<Object>集合对象属性拷贝工具类
    uni-app--》基于小程序开发的电商平台项目实战(四)
    Softing工业将亮相2022年阿赫玛展会
    大盘向好、交付企稳,金科股份中报里的关键信号
    【从零开始学习 SystemVerilog】11.3、SystemVerilog 断言—— Concurrent Assertions(并发断言)
  • 原文地址:https://blog.csdn.net/weixin_45699541/article/details/126300714