Zookeeper 系列 (四)

Zookeeper 系列 (四)

一 、集群搭建

Zookeeper 集群中的角色

在这里插入图片描述

在这里插入图片描述

设计目的:

1 、 最终一致性: client 不论连接到哪个 Server ,展示个他的都是同一个视图

2 、可靠性:具有简单、健壮、良好的性能,如果消息m 被一台服务器接受,那么它 将被所有的服务器接受。

3、实时性:zookeeper 保证客户端将在一定的时间间隔内获取到服务器更新的消息

4、等待无关(wait-free): 每个client 都能有效等待

5、原子性:更新只能成功或失败!

6、顺序性: 全局有序 和 偏序

测试 伪集群

说明:一台机器上启动 多个 zookeeper 实例组成集群

需要有多个zookeeper 服务 只改变端口 就是伪服务

1、复制配置文件 命名为 zoo-1.cfg 修改配置文件

显示更改配置文件

dataDir=/tmp/zookeeper-1
clientPort=2181
server.1=192.168.245.128:2881:3881
server.2=192.168.245.128:2882:3882
server.3=192.168.245.128:2883:3893

2、复制配置文件 命名为 zoo-2.cfg 修改

dataDir=/tmp/zookeeper-2
clientPort=2182

3、复制配置文件 命名为 zoo-3.cfg 修改

dataDir=/tmp/zookeeper-3
clientPort=2183

4、在 [ /tmp/zookeeper-1,/tmp/zookeeper-2,/tmp/zookeeper-3] 中 创建文件 myid 文件 写入当前实例的 servier id

# cd /tmp/zookeeper-1
# vim myid
1

# cd /tmp/zookeeper-2
# vim myid
2

# cd /tmp/zookeeper-3
# vim myid
3

5、启动三个 zookeeper 实例

# bin/zkServer.sh start conf/zoo-1.cfg
# bin/zkServer.sh start conf/zoo-2.cfg
# bin/zkServer.sh start conf/zoo-3.cfg

6、直接用 zkCli.sh -server IP:PORT 连接 zookeeper 服务端检测

这里是使用了三个客户端
在这里插入图片描述

二、zookeeper 分布式锁

参考链接:https://blog.youkuaiyun.com/liyiming2017/article/details/83786331

1.0 版本

思路:

  1. 用zookeeper中一个临时节点代表锁,比如在==/exlusive_lock下创建临时子节点/exlusive_lock/lock==。
  2. 所有客户端争相创建此节点,但只有一个客户端创建成功。
  3. 创建成功代表获取锁成功,此客户端执行业务逻辑
  4. 未创建成功的客户端,监听/exlusive_lock变更
  5. 获取锁的客户端执行完成后,删除/exlusive_lock/lock,表示锁被释放
  6. 锁被释放后,其他监听/exlusive_lock变更的客户端得到通知,再次争相创建临时子节点==/exlusive_lock/lock==。此时相当于回到了第2步。

问题:

  1. 锁的获取顺序和最初客户端争抢顺序不一致,这不是一个公平锁。每次锁获取都是当次最先抢到锁的客户端。
  2. 羊群效应,所有没有抢到锁的客户端都会监听/exlusive_lock变更。当并发客户端很多的情况下,所有的客户端都会接到通知去争抢锁,此时就出现了羊群效应。

2.0版本

思路:

  1. 每个客户端往/exlusive_lock下创建有序临时节点/exlusive_lock/lock_。创建成功后/exlusive_lock下面会有每个客户端对应的节点,如/exlusive_lock/lock_000000001
  2. 客户端取得/exlusive_lock下子节点,并进行排序,判断排在最前面的是否为自己。
  3. 如果自己的锁节点在第一位,代表获取锁成功,此客户端执行业务逻辑
  4. 如果自己的锁节点不在第一位,则监听自己前一位的锁节点。例如,自己锁节点lock_000000002,那么则监听lock_000000001.
  5. 当前一位锁节点(lock_000000001)对应的客户端执行完成,释放了锁,将会触发监听的客户端(lock_000000002)的逻辑。
  6. 监听客户端重新执行第2步逻辑,判断自己是否获得了锁。

实现代码 LockSample 类 2.0 版本

分布式锁:

public class LockSampleDme {
    //客户端
    private ZooKeeper zkClient;
    //路径
    private static final String LOCK_ROOT_PATH = "/Locks";
    //节点名
    private static final String LOCK_NODE_NAME = "Lock_";
    private static final String IP_HOST = "192.168.245.128:2181";
    private String lockPath;

    private Watcher watcher = new Watcher() {
        @Override
        public void process(WatchedEvent event) {
            System.out.println(event.getPath() + " 前锁释放");
            synchronized (this) {
                notifyAll();
            }
        }
    };

    public LockSampleDme() throws IOException {

        zkClient = new ZooKeeper(IP_HOST, 5000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                if (event.getState() == Event.KeeperState.Disconnected) {
                    System.out.println("失去连接");
                }
            }
        });
    }


    /**
     * 创建节点
     * 先判断锁的根节点/Locks是否存在
     * 不存在的话创建
     * 然后在/Locks下创建有序临时节点,并设置当前的锁路径变量lockPath
     */
    private void createLock() throws KeeperException, InterruptedException {
        /*
        判断 根节点 /Locks 是否存在
         */
        Stat exists = zkClient.exists(LOCK_ROOT_PATH, false);
        if (exists == null) {
            //不存在 就创建
            zkClient.create(LOCK_ROOT_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }

        /*
        然后在/Locks下创建有序临时节点  并设置当前的锁路径变量lockPath
         */
        //返回的 是 路径
        String path = zkClient.create(LOCK_ROOT_PATH + "/" + LOCK_NODE_NAME,
                new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

        System.out.println(Thread.currentThread().getName() + "锁路径:" + path);
        this.lockPath = path;
    }

    /**
     *
     */
    private void attemptLock() throws Exception {
        //获取 /Locks 下的所有的 子节点  list 内部值   Lock_0000000000 类型
        List<String> lockPaths = zkClient.getChildren(LOCK_ROOT_PATH, false);
        //节点 序号排序 stream 流方式
        lockPaths = lockPaths.stream().sorted((o1, o2) -> o1.compareTo(o2)).collect(Collectors.toList());

        // 进行获取 自己创建的 锁 的 地址 lockPath  需要去除 头部的 /Locks/ 获取所处位置
        int index = lockPaths.indexOf(lockPath.substring(LOCK_ROOT_PATH.length() + 1));

        if (index == 0) {
            //第一位 获取到锁
            System.out.println(Thread.currentThread().getName() + " 锁获得 " + lockPath);
            return;
        } else {
            //没有获取到锁 进行 监听 前一个
            String prevPath = lockPaths.get(index - 1);
            // 判断是否存在进行 监听
            Stat exists = zkClient.exists(LOCK_ROOT_PATH + "/" + prevPath, watcher);

            //前一个 节点 不存在 或掉线 进行 重新获取锁
            if (exists == null) {
                attemptLock();
            } else {
                //存在阻塞 当前 线程 直到 prevPath   锁释放  被watcher  监听 ,notifyAll 唤醒 重新 attemptLock
                System.out.println("等待前锁释放 " + prevPath);
                synchronized (watcher) {
                    watcher.wait();
                }
                attemptLock();
            }
        }
    }

    //业务逻辑
    public void acquireLock() throws Exception {
        //创建 节点
        createLock();
        //尝试 获取 锁
        attemptLock();
    }


    //释放锁
    public void releaseLock() throws KeeperException, InterruptedException {
        zkClient.delete(lockPath, -1);
        zkClient.close();
        System.out.println("锁释放:" + lockPath);
    }
}

客户端创建:

class TicketSeller {
    public void sell() {
        System.out.println("开始售卖:");
        //线程 随机休眠 模拟 现实操作
        int sleepMillis = (int) (Math.random() * 2000);

        try {
            Thread.sleep(sleepMillis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("售票结束!");
    }

    public void sellTicketWithLock() throws Exception {
        LockSampleDme lockSampleDme = new LockSampleDme();
        lockSampleDme.acquireLock();//进行排队
        sell();//等待
        lockSampleDme.releaseLock();//买到 退出
    }

    public static void main(String[] args) throws Exception {
        TicketSeller ticketSeller = new TicketSeller();
        for (int i = 0; i < 10; i++) {
            ticketSeller.sellTicketWithLock();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值