互联网架构-分布式协调工具Zookeeper-054:基于Zookeeper实现分布式锁

1 Zookeeper基础知识点回顾

课程内容:
1.Zookeeper事件通知作用
2.Zookeeper实现分布式锁思路
3.传统同步锁存在哪些问题
4.基于模版方法设计模式设计一套史上最牛逼分布式锁

Zookeeper总结 类似于文件存储系统可以解决分布式领域中遇到问题

Zookeeper分布式协调工具
特征:
1.定义的节点包含key(路径)和value ,路径不允许有重复,保证唯一性;
2.Zookeeper节点分为四种类型:持久、持久序号、临时、临时序号;
3.持久与临时节点区别: 连接一旦关闭,当前的节点是否自动删除;
4.事件通知 监听节点发生的变化,删除、修改、子节点变化等。

2 Zookeeper实现事件监听通知

public class Test001 {

    //参数1 连接地址
    private static final String ADDRES = "192.168.0.53:2181";
    // 参数2 zk超时时间
    private static final int TIMEOUT = 5000;

    public static void main(String[] args) {
        // 创建了一个父节点 /mayikt-service/8080|8081
        // 1.创建zk节点
        ZkClient zkClient = new ZkClient(ADDRES, TIMEOUT);
        String parentPath = "/mayikt-service";
        // 2.监听子节点是否发生变化,如果发生变化都可以获取到回调通知
        zkClient.subscribeChildChanges(parentPath, new IZkChildListener() {
            @Override
            public void handleChildChange(String s, List<String> list) throws Exception {
                System.out.println("s:" + s + ",节点发生了变化");
                list.forEach(System.out::println);
            }
        });
        // 3.监听节点的value值是否发生变化
        zkClient.subscribeDataChanges(parentPath, new IZkDataListener() {

            @Override
            public void handleDataChange(String s, Object o) throws Exception {
                System.out.println("s:" + s + ",o:" + o);
            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                System.out.println("s被删除:" + s);
            }
        });

        // 修改值内容
        zkClient.writeData(parentPath, "meite_mayikt");
        while (true){
        }
    }
}

测试结果:
在这里插入图片描述

3 多线程情况下生成订单号码存在哪些问题

分布式锁实现思路:
1.基于数据库实现分布式锁
2.基于Redis实现分布式锁
3.基于Zk实现分布式锁
4.基于redisson实现分布式锁
举例:假设当前生成订单号是由时间戳+序号组成;
多台节点同时生成订单号开始的时间相等,时间戳可能相同违背幂等性原则;

模拟单个jvm生成订单号线程安全问题

public class OrderNumGenerator {

    /**
     * 序号
     */
    private static int count;

    /**
     * 生成我们的时间戳 为订单号码
     *
     * @return
     */
    public String getNumber() {
        SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
        try {
            Thread.sleep(30);
        } catch (Exception e) {

        }
        return simpt.format(new Date()) + "-" + ++count;
    }
}
public class OrderService implements Runnable {

    private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();

    @Override
    public void run() {
        getNumber();
    }

    private void getNumber() {
        String number = orderNumGenerator.getNumber();
        System.out.println(Thread.currentThread().getName() + ",获取的number:" + number);

    }
}
public class Test002 {
    public static void main(String[] args) {
        OrderService orderService = new OrderService();
        for (int i = 0; i < 100; i++) {
            new Thread(orderService).start();
        }
    }
}

运行结果:
在这里插入图片描述
单个jvm多线程生成订单号码重复解决办法:使用synchronized或者lock锁

4 Zookeeper实现分布式锁基本设计思路

如果在多个jvm中同时生成订单号码发生重复如何解决?
synchronized或者lock锁只在本地jvm中有效。
分布式锁基本的概念:
解决在多个jvm中最终只能够有一个jvm进行执行。

Zookeeper实现分布式锁的思路:
节点保证唯一、事件通知、临时节点(生命周期和Session会话关联)
创建分布式锁原理:
1.多个jvm同时在Zookeeper上创建相同的临时节点(lockPath)
2.因为临时节点路径保证唯一性,只要谁能够创建成功谁就能够获取锁,就可以开始执行业务逻辑;
3.如果节点已经被其他请求创建或者是创建节点失败,当前的请求实现等待;
释放锁的原理:临时节点+事件通知
1.因为采用zk临时节点,当前节点创建成功,表示获取锁成功;正常执行完业务逻辑调用Session关闭连接方法,当前的节点会被删除;----释放锁
2.其他正在等待的请求采用事件监听,如果当前节点被删除的话,又重新进入到获取锁流程;

5 使用模版方法设计模式定义共同锁骨架

public interface Lock {

    /**
     * 获取锁
     */
    public void getLock();

    /**
     * 释放锁
     */
    public void unLock();
}
public abstract class AbstractTemplateLock implements Lock {

    @Override
    public void getLock() {
        // 模板方法,定义共同抽象骨架
        if (tryLock()) {
            System.out.println(">>>" + Thread.currentThread().getName() + ",获取锁成功");
        } else {
            //开始实现等待
            waitLock();// 事件监听
            // 重新获取锁
            getLock();
        }
    }

    /**
     * 获取锁
     *
     * @return
     */
    protected abstract boolean tryLock();

    /**
     * 等待锁
     *
     * @return
     */
    protected abstract void waitLock();

    protected abstract void releaseLock();

    @Override
    public void unLock() {
        releaseLock();
    }
}

6 Zookeeper实现分布式锁代码实现

public class ZkTemplateImplLock extends AbstractTemplateLock {

    //参数1 连接地址
    private static final String ADDRES = " 192.168.0.53:2181";
    // 参数2 zk超时时间
    private static final int TIMEOUT = 5000;
    // 创建zk连接
    ZkClient zkClient = new ZkClient(ADDRES, TIMEOUT);
    // 共同创建的临时节点路径
    private String lockPath = "/lockPath";
    private CountDownLatch countDownLatch = null;

    @Override
    protected boolean tryLock() {
        // 获取锁的思想:多个jvm同时创建临时节点,谁创建成功谁获取锁
        try {
            zkClient.createEphemeral(lockPath);
            return true;
        } catch (Exception e) {
            // 如果创建的节点已经存在,走异常
//            e.printStackTrace();
            return false;
        }
    }

    @Override
    protected void waitLock() {
        // 1.使用事件监听监听lockPath节点是否被删除,如果被删除,重新进入到获取锁的权限
        IZkDataListener iZkDataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {
            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                if (countDownLatch != null) {
                    countDownLatch.countDown();// 计数器变为0
                }
            }
        };
        zkClient.subscribeDataChanges(lockPath, iZkDataListener);
        // 2.使用countDownLatch等待
        if (countDownLatch == null) {
            countDownLatch = new CountDownLatch(1);
        }
        try {
            countDownLatch.await();// 如果当前计数器不为0一直等待
        } catch (Exception e) {

        }
        // 3.如果当前节点被删除,重新进入获取锁
        // 删除事件监听
        zkClient.unsubscribeDataChanges(lockPath, iZkDataListener);
    }

    @Override
    protected void releaseLock() {
        // 关闭zk连接
        if (zkClient != null) {
            zkClient.close();
            System.out.println(Thread.currentThread().getName() + ",释放锁>>>");
        }
    }
}

7 Zookeeper效果整体演示

public class OrderService implements Runnable {

    private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();

    private Lock lock = new ZkTemplateImplLock();

    @Override
    public void run() {
        getNumber();
    }

    private void getNumber() {
        try {
            lock.getLock();
            String number = orderNumGenerator.getNumber();
            System.out.println(Thread.currentThread().getName() + ",获取的number:" + number);
        } catch (Exception e) {
        } finally {
            lock.unLock();
        }
    }
}
public class Test003 {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new OrderService()).start();
        }
    }
}

运行结果:
在这里插入图片描述

8 Zookeeper超时了,如何防止死锁问题

如果zk超时了,有做数据库写的操作统一直接回滚

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值