手写Zookeeper分布式锁

2 篇文章 0 订阅
1 篇文章 0 订阅

在说Zookeeper做分布式锁之前,我们知道Redis也可以做分布式锁。那我为什么要用Zookeeper做分布式锁呢?

上图为数据库,Redis,Zookeeper实现分布式锁的技术对比。不用说,数据库实现分布式锁的性能肯定很低,Redis虽然性能很高,但是最终一致性上却是输于Zookeeper。Zookeeper在分布式集群上有天然优势。在生产环境中,中间件一般以集群形式部署,那么这里涉及到主从同步问题,Redis主从集群中,如果Master节点在向Slave节点发送RDB文件同步时,有可能Master节点挂掉,文件里面的数据有可能会有一秒的丢失。然而Redis有一套同步确认的策略。用Redis上锁实际是Redis里面setnx 数据,一般需要主节点同步数据给从节点成功了,才能认为成功。当然为了提升效率,有一个RedLock模型,该模型认为只要同步节点成功数超过半数就认为上锁成功。然而这个RedLock模型算法维护起来相当麻烦。但是Zookeeper天然支持这个模型。

还有一个用Zookeeper的原因在于其有一个watcher机制,线程抢不到锁时阻塞,当持有锁的线程释放锁时,这个watcher机制会主动通知其他线程唤醒去抢锁。而Redis的锁,线程抢锁的过程是一个自旋setnx K-V的过程,这种抢锁没有Zookeeper的抢锁方式优雅。所以我喜欢用Zookeeper实现分布式锁。

 

Zookeeper特性

 

 

1.节点树数据结构,znode是一个跟Unix文件系统路径相似的节点,可以往这个节点存储或获取数据;
2.通过客户端可对znode进行增删改查的操作,还可以注册watcher监控znode的变化。

通过上面两个特性可以实现分布式锁。

 

上图为Zookeeper实现分布式锁的流程图。下面为代码的实现。

代码实现

 

创建一个ZkDistributeLock类实现Lock接口

@Slf4j
public class ZkDistributeLock implements Lock {

    @Autowired
    private ZookeeperConfig config;

    private String lockPath;

    private ZkClient client;

    private String currentPath;

    private String beforePath;
    ...
}

 

构造函数里面创建锁的根节点。

public ZkDistributeLock(String lockPath) {
    super();
    this.lockPath = lockPath;
    ZookeeperConfig zookeeperConfig = new ZookeeperConfig();
    this.client = zookeeperConfig.getConnectionWithoutSpring();
    this.client.setZkSerializer(new MyZkSerializer());

    if (!this.client.exists(lockPath)) {
        try {
            this.client.createPersistent(lockPath);
        } catch (ZkNodeExistsException e) {
            e.getStackTrace();
        }
    }
}

 

lock()方法编写

@Override
public void lock() {

    if (!tryLock()) {
        //没获得锁,阻塞自己
        waitForLock();
        //再次尝试
        lock();
    }

}

tryLock()方法编写

public boolean tryLock() { 
//不会阻塞 //创建节点 
if (this.currentPath == null) { 
    currentPath = this.client.createEphemeralSequential(lockPath + "/", "lock"); 
} 
List<String> children = this.client.getChildren(lockPath); 
Collections.sort(children); 
if (currentPath.equals(lockPath + "/" + children.get(0))) { 
    return true; 
} else {
     int currentIndex = children.indexOf(currentPath.substring(lockPath.length() + 1));             
     beforePath = lockPath + "/" + children.get(currentIndex - 1); 
} 
log.info("锁节点创建成功:{}", lockPath); 
return false;
}

unlock()方法编写

@Override
public void unlock() {
    client.delete(this.currentPath);
}

waitForLock()方法编写

private void waitForLock() {

    CountDownLatch count = new CountDownLatch(1);
    IZkDataListener listener = new IZkDataListener() {
        @Override
        public void handleDataChange(String s, Object o) throws Exception {

        }

        @Override
        public void handleDataDeleted(String s) throws Exception {
            System.out.println(String.format("收到节点[%s]被删除了",s));
            count.countDown();
        }
    };

    client.subscribeDataChanges(this.beforePath, listener);

    //自己阻塞自己
    if (this.client.exists(this.beforePath)) {
        try {
            count.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //取消注册
    client.unsubscribeDataChanges(this.beforePath, listener);
}

 

测试

//创建订单时加锁
@Override
public void createOrder() {

    String lockPath = "/distribute_lock";
    String orderCode = null;
    //zk 分布式锁
    Lock lock = new ZkDistributeLock(lockPath);
    // zkDistributeLock.setLockPath(lockPath);
    lock.lock();
    try {
        orderCode = ocg.getOrderCode();
    } finally {
        lock.unlock();
    }

    log.info("当前线程:{},生成订单编号:{}",Thread.currentThread().getName() , orderCode);
    //其他逻辑

}

@Test
public void testDisLock() {
    //并发数
    int currency = 20;

    //循环屏障
    CyclicBarrier cyclicBarrier = new CyclicBarrier(currency);

    for (int i = 0; i < currency; i++) {
        new Thread(() -> {
            // OrderServiceImplWithDisLock orderService = new OrderServiceImplWithDisLock();
            System.out.println(Thread.currentThread().getName() + "====start====");
            //等待一起出发
            try {
                //CyclicBarrier共享锁模拟并发
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            orderService.createOrder();
        }).start();

    }
}

 

运行test方法,zookeeper上面会生成锁节点。

 

线程抢到锁,会显示创建锁节点成功

 

并且执行锁里面的代码

 

抢不到锁,会阻塞,等待被释放的锁

 

释放锁时,节点删除,wacther机制通知

抢到锁的线程,执行当前线程里面的任务

 

github代码:【阅读原文】可见

往期推荐

扫码二维码,获取更多精彩。或微信搜Lvshen_9,可后台回复获取资料

1.回复"java" 获取java电子书;

2.回复"python"获取python电子书;

3.回复"算法"获取算法电子书;

4.回复"大数据"获取大数据电子书;

5.回复"spring"获取SpringBoot的学习视频。

6.回复"面试"获取一线大厂面试资料

7.回复"进阶之路"获取Java进阶之路的思维导图

8.回复"手册"获取阿里巴巴Java开发手册(嵩山终极版)

9.回复"总结"获取Java后端面试经验总结PDF版

10.回复"Redis"获取Redis命令手册,和Redis专项面试习题(PDF)

11.回复"并发导图"获取Java并发编程思维导图(xmind终极版)

另:点击【我的福利】有更多惊喜哦。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当面试中涉及到ZooKeeper分布式锁的问题,通常会涉及以下几个方面: 1. 什么是ZooKeeper分布式锁ZooKeeper分布式锁是基于ZooKeeper提供的原语实现的一种分布式锁机制。它利用了ZooKeeper的有序临时节点和Watcher机制来实现锁的竞争和释放。 2. ZooKeeper分布式锁的实现原理是什么? ZooKeeper分布式锁的实现原理主要依赖于ZooKeeper的有序临时节点和Watcher机制。当一个线程需要获取锁时,它会在ZooKeeper的指定路径上创建一个有序临时节点,并且注册一个Watcher来监听前一个节点是否存在。如果前一个节点不存在,则该线程获取锁成功;否则,该线程需要等待前一个节点被删除后继续竞争锁。 3. ZooKeeper分布式锁存在的问题有哪些? ZooKeeper分布式锁虽然实现了基本的锁机制,但仍然存在以下问题: - 网络延迟:由于网络延迟等原因,可能导致锁的竞争时间增加,影响系统的性能。 - 节点故障:如果持有锁的节点发生故障,可能导致其他节点无法获取锁或长时间等待。 - 死锁:如果在获取锁的过程中发生故障或异常,可能导致死锁情况的发生。 4. 如何解决ZooKeeper分布式锁的问题? 为了解决ZooKeeper分布式锁存在的问题,可以采取以下策略: - 设置合理的超时时间,避免长时间等待导致系统性能下降。 - 使用心跳机制来检测节点的存活状态,及时处理节点故障。 - 采用分布式协调框架或工具,如Curator、Spring Integration等,简化分布式锁的使用和管理。 这些是一些常见的ZooKeeper分布式锁面试题及其答案,希望能对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值