ZooKeeper分布式锁简单实践

ZooKeeper分布式锁的实现原理

在分布式解决方案中,Zookeeper是一个分布式协调工具。当多个JVM客户端,同时在ZooKeeper上创建相同的一个临时节点,因为临时节点路径是保证唯一,只要谁能够创建节点成功,谁就能够获取到锁。没有创建成功节点,就会进行等待,当释放锁的时候,采用事件通知给客户端重新获取锁资源。如果请求超时直接返回给客户端超时,重新请求即可。

代码实现
为了更好的展现效果,我这里设置每个线程请求需要1s,请求超时时间为30s。

首先我们先写一个测试类,模拟多线程多客户端请求的情况:

public class ZkLockTest implements Runnable {
private ZkLock zkLock = new ZkDistributedLock();

public void run() {
    try {
        if (zkLock.getLock((long)30000,null)) {
            System.out.println("线程:" + Thread.currentThread().getName() + ",抢购成功:" + System.currentTimeMillis());
        } else {
            System.out.println("线程:" + Thread.currentThread().getName() + ",抢购超时失败请重试:" + System.currentTimeMillis());
        }
        Thread.sleep(1000);
    } catch (Exception e) {

    } finally {
        zkLock.unLock();
    }
}

public static void main(String[] args) {
    System.out.println("zk分布式锁开始。。");
    for (int i = 0; i < 100; i++) {
        new Thread(new ZkLockTest()).start();
    }
}

}
模拟100个线程,去同时争夺锁。当然上述写法 100个线程不会同时启动,如果需要的话可以用信号量的形式控制。

其次,写一个锁的接口

public interface ZkLock {

// 获取锁
Boolean getLock(Long acquireTimeout,Long endTime);

// 释放锁
void unLock();

}
这里我定义了两个接口,分别对应获取锁和释放锁。

在获取锁中有两个参数,含义分别为锁超时时间和最终计算的超时时间,具体看下文代码就懂了。

public class ZkDistributedLock implements ZkLock {
// 集群连接地址
private String CONNECTION = "127.0.0.1:2181";
// zk客户端连接
private ZkClient zkClient = new ZkClient(CONNECTION);
// path路径
private String lockPath = "/lock";
private CountDownLatch countDownLatch;
//请求设置的超时时间:acquireTimeout 毫秒。最终超时时间endTime
public Boolean getLock(Long acquireTimeout,Long endTime) {
Boolean lock = false;
if (endTime == null) {
//等待超时时间
endTime = System.currentTimeMillis() + acquireTimeout;
}
if (tryLock()) {
System.out.println("####获取锁成功######");
lock = true;
} else {
if (waitLock(endTime)) {
if (getLock(null,endTime)) {
lock = true;
}
}
}
return lock;
}

public void unLock() {
    if (zkClient != null) {
        System.out.println("#######释放锁#########");
        zkClient.close();
    }
}

private boolean tryLock() {
    try {
        zkClient.createEphemeral(lockPath);
        return true;
    } catch (Exception e) {

        return false;
    }

}

private Boolean waitLock(Long endTime) {
   // System.out.println("进入等待");
    // 使用zk临时事件监听
    IZkDataListener iZkDataListener = null;
    try {
        // 使用zk临时事件监听
        iZkDataListener = new IZkDataListener() {

            public void handleDataDeleted(String path) throws Exception {
                if (countDownLatch != null) {
                    countDownLatch.countDown();
                }
            }
            public void handleDataChange(String arg0, Object arg1) throws Exception {

            }
        };
        // 注册事件通知
        zkClient.subscribeDataChanges(lockPath, iZkDataListener);
        if (System.currentTimeMillis() < endTime) {
            if (zkClient.exists(lockPath)) {
                countDownLatch = new CountDownLatch(1);
                try {
                    countDownLatch.await();
                    return true;
                } catch (Exception e) {

                }
            } else {
                return true;
            }
        } else {
            System.out.println("超时返回");
        }
    } catch (Exception e) {

    } finally {
        // 监听完毕后,移除事件通知
        zkClient.unsubscribeDataChanges(lockPath, iZkDataListener);
    }
    return false;
}

}
这个类是我实现zk锁的核心类,和上文原理图中类似。首先用户请求的时候需要获取锁,第一个争夺到锁的用户执行相关逻辑后释放锁,在这个过程中如果程序出错断开连接,因为临时节点的缘故,节点也会自动删除释放锁的。

另外就是其他争夺锁失败的用户,我这里设置了一定的等待时间,当在时间内原锁释放,还是可以重新去获取锁的。这里要说下锁释放的监听,在原生的zookeeper中,使用watcher需要每次先注册,而且使用一次就需要注册一次。而在zkClient中,没有注册watcher的必要,而是引入了listener的概念,即只要client在某一个节点中注册了listener,只要服务端发生变化,就会通知当前注册listener的客户端。我这里使用的是IZkDataListener,这个类是zkClient提供的一个接口,它可以在当前节点数据内容或版本发生变化或者当前节点被删除时触发。

触发后我们就可以重新去争夺锁,当再次争夺失败进入等待时会再次检测当前请求是否超时。

下面我们来看下上述代码的实现效果:

zk分布式锁开始。。
####获取锁成功######
线程:Thread-3,抢购成功:1544183770509
#######释放锁#########
####获取锁成功######
线程:Thread-81,抢购成功:1544183771555
#######释放锁#########

.........

超时返回
线程:Thread-11,抢购超时失败请重试:1544183800677
超时返回
线程:Thread-1,抢购超时失败请重试:1544183800681
#######释放锁#########
#######释放锁#########
####获取锁成功######
线程:Thread-49,抢购成功:1544183801710
超时返回
线程:Thread-25,抢购超时失败请重试:1544183801729
超时返回
#######释放锁#########
#######释放锁#########
释放锁说的可能并不准确,应该说是关闭连接,有些线程实际上是没有得到锁的。

简单尝试了下zk实现分布式锁的方式,当然上述代码如果应用到生产中肯定问题还是不少的,因为兴趣点不在这,就不仔细研究了。简单来说,相比其他方式实现步骤更为复杂,感觉更容易出问题。

总结
经过三种方式的应用和简单实践,总结实现分布式锁三种方式的优缺点如下

1、数据库实现:

优点,实现简单只是for update的显示加锁。缺点,性能问题较大,而且本身系统在设计时是需要尽量减轻数据库的压力的。

2、Redis实现:

优点:一般互联网项目都会集成,本身是nosql数据库,缓存实现简单,高并发应付自如,同时新版的Jedis完美解决了以往程序出错,未设置超时时间死锁的问题。

缺点:网络问题可能会引起锁删除失败,超时时间有一定的延迟。

3、ZooKeeper实现:

优点:Zookeeper临时节点先天可控的有效期设置,避免了程序引发的死锁问题

缺点:实现过于繁杂,相比其他两种写法更容易出问题,另外还需要单独维护zk。

结论:

我个人更为推荐Redis的实现方式,实现简单,性能也比较好,同时引入集群可以提高可用性。Jedis多参的设置方式也较好的保证了有效期的控制和死锁的问题

原文地址:http://blog.51cto.com/14028890/2327872

时间: 2024-10-13 23:32:52

ZooKeeper分布式锁简单实践的相关文章

[转载] zookeeper 分布式锁服务

转载自http://www.cnblogs.com/shanyou/archive/2012/09/22/2697818.html 分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那一层来挡.当大量的行锁.表锁.事务充斥着数据库的时候.一般web应用很多的瓶颈都在数据库上,这里给大家介绍的是减轻数据库锁负担的一种方案,使用zookeeper分布式锁服务. zookeeper是hadoop下面的一个子项目, 用来协调跟hadoop相关的一些分布式的框架, 如hadoop, hiv

分布式锁(一) Zookeeper分布式锁

什么是Zookeeper? Zookeeper(业界简称zk)是一种提供配置管理.分布式协同以及命名的中心化服务,这些提供的功能都是分布式系统中非常底层且必不可少的基本功能,但是如果自己实现这些功能而且要达到高吞吐.低延迟同时还要保持一致性和可用性,实际上非常困难.因此zookeeper提供了这些功能,开发者在zookeeper之上构建自己的各种分布式系统. 虽然zookeeper的实现比较复杂,但是它提供的模型抽象却是非常简单的.Zookeeper提供一个多层级的节点命名空间(节点称为znod

关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在jdk java.util 并发包中已经为我们提供了这些方法去加锁, 比如synchronized 关键字 或者Lock 锁,都可以处理. 但是我们现在的应用程序如果只部署一台服务器,那并发量是很差的,如果同时有上万的请求那么很有可能造成服务器压力过大,而瘫痪. 想想双十一 和 三十晚上十点分支付宝红

Curator实现zookeeper分布式锁的基本原理

一.写在前面 之前写过一篇文章(<拜托,面试请不要再问我Redis分布式锁的实现原理>),给大家说了一下Redisson这个开源框架是如何实现Redis分布式锁原理的,这篇文章再给大家聊一下ZooKeeper实现分布式锁的原理. 同理,我是直接基于比较常用的Curator这个开源框架,聊一下这个框架对ZooKeeper(以下简称zk)分布式锁的实现. 一般除了大公司是自行封装分布式锁框架之外,建议大家用这些开源框架封装好的分布式锁实现,这是一个比较快捷省事儿的方式. 二.ZooKeeper分布

彻底讲清楚ZooKeeper分布式锁的实现原理

一.写在前面 之前写过一篇文章(<拜托,面试请不要再问我Redis分布式锁的实现原理>),给大家说了一下Redisson这个开源框架是如何实现Redis分布式锁原理的,这篇文章再给大家聊一下ZooKeeper实现分布式锁的原理. 同理,我是直接基于比较常用的Curator这个开源框架,聊一下这个框架对ZooKeeper(以下简称zk)分布式锁的实现. 一般除了大公司是自行封装分布式锁框架之外,建议大家用这些开源框架封装好的分布式锁实现,这是一个比较快捷省事儿的方式. 二.ZooKeeper分布

ZooKeeper分布式锁浅谈(一)

一.概述 清明节的时候写了一篇分布式锁概述,里面介绍了分布式锁实现的几种方式,其实那时候我一直沉迷于使用redis的悲观锁和乐观锁来实现分布式锁,直到一个血案的引发才让我重新认识了redis分布式锁的弊端,所以才痛定思痛潜心研究Zookeeper:自己装了三台Centos虚拟机,搭建了ZooKeeper集群.二.ZooKeeper基本概念1.前言 ZooKeeper 是一个开源的分布式协调服务,由雅虎创建,是 Google Chubby 的开源实现.分布式应用程序可以基于 ZooKeeper 实

死磕 java同步系列之zookeeper分布式锁

问题 (1)zookeeper如何实现分布式锁? (2)zookeeper分布式锁有哪些优点? (3)zookeeper分布式锁有哪些缺点? 简介 zooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它可以为分布式应用提供一致性服务,它是Hadoop和Hbase的重要组件,同时也可以作为配置中心.注册中心运用在微服务体系中. 本章我们将介绍zookeeper如何实现分布式锁运用在分布式系统中. 基础知识 什么是znode? zooKeeper操作和维护的为一个个数据节点,称为 z

高级java必会系列一:zookeeper分布式锁

方案1: 算法思路:利用名称唯一性,加锁操作时,只需要所有客户端一起创建/test/Lock节点,只有一个创建成功,成功者获得锁.解锁时,只需删除/test/Lock节点,其余客户端再次进入竞争创建节点,直到所有客户端都获得锁.特点:这种方案的正确性和可靠性是ZooKeeper机制保证的,实现简单.缺点是会产生"惊群"效应,假如许多客户端在等待一把锁,当锁释放时候所有客户端都被唤醒,仅仅有一个客户端得到锁.方案2:算法思路:临时顺序节点实现共享锁        客户端调用create(

zookeeper 分布式锁原理:

1 大家也许都很熟悉了多个线程或者多个进程间的共享锁的实现方式了,但是在分布式场景中我们会面临多个Server之间的锁的问题,实现的复杂度比较高.利用基于google chubby原理开发的开源的zookeeper,可以使得这个问题变得简单很多.下面介绍几种可能的实现方式,并且对比每种实现方式的优缺点. 1. 利用节点名称的唯一性来实现共享锁 ZooKeeper抽象出来的节点结构是一个和unix文件系统类似的小型的树状的目录结构.ZooKeeper机制规定:同一个目录下只能有一个唯一的文件名.例