1.定义分布式锁接口

package com.ljq.lock;

import java.util.concurrent.TimeUnit;

public interface DistributedLock {
/**
* 获取锁,如果没有得到锁就一直等待
*
* @throws Exception
*/
public void acquire() throws Exception; /**
* 获取锁,如果没有得到锁就一直等待直到超时
*
* @param time 超时时间
* @param unit time参数时间单位
*
* @return 是否获取到锁
* @throws Exception
*/
public boolean acquire(long time, TimeUnit unit) throws Exception; /**
* 释放锁
*
* @throws Exception
*/
public void release() throws Exception;
}

2.定义一个简单的互斥锁
定义一个互斥锁类,实现以上定义的锁接口,同时继承一个基类BaseDistributedLock,该基类主要用于与Zookeeper交互,包含一个尝试获取锁的方法和一个释放锁。

package com.ljq.lock;

import java.io.IOException;
import java.util.concurrent.TimeUnit; import org.I0Itec.zkclient.ZkClient; public class SimpleDistributedLock extends BaseDistributedLock implements DistributedLock { /*
* 用于保存Zookeeper中实现分布式锁的节点,如名称为locker:/locker,
* 该节点应该是持久节点,在该节点下面创建临时顺序节点来实现分布式锁
*/
private final String basePath; /*
* 锁名称前缀,locker下创建的顺序节点例如都以lock-开头,这样便于过滤无关节点
* 这样创建后的节点类似:lock-00000001,lock-000000002
*/
private static final String LOCK_NAME = "lock-"; /* 用于保存某个客户端在locker下面创建成功的顺序节点,用于后续相关操作使用(如判断) */
private String ourLockPath; /**
* 传入Zookeeper客户端连接对象,和basePath
*
* @param client
* Zookeeper客户端连接对象
* @param basePath
* basePath是一个持久节点
*/
public SimpleDistributedLock(ZkClient client, String basePath) {
/*
* 调用父类的构造方法在Zookeeper中创建basePath节点,并且为basePath节点子节点设置前缀
* 同时保存basePath的引用给当前类属性
*/
super(client, basePath, LOCK_NAME);
this.basePath = basePath;
} /**
* 用于获取锁资源,通过父类的获取锁方法来获取锁
*
* @param time 获取锁的超时时间
* @param unit 超时时间单位
*
* @return 是否获取到锁
* @throws Exception
*/
private boolean internalLock(long time, TimeUnit unit) throws Exception {
// 如果ourLockPath不为空则认为获取到了锁,具体实现细节见attemptLock的实现
ourLockPath = attemptLock(time, unit);
return ourLockPath != null;
} /**
* 获取锁,如果没有得到锁就一直等待
*
* @throws Exception
*/
public void acquire() throws Exception {
// -1表示不设置超时时间,超时由Zookeeper决定
if (!internalLock(-1, null)) {
throw new IOException("连接丢失!在路径:'" + basePath + "'下不能获取锁!");
}
} /**
* 获取锁,如果没有得到锁就一直等待直到超时
*
* @param time 超时时间
* @param unit time参数时间单位
*
* @return 是否获取到锁
* @throws Exception
*/
public boolean acquire(long time, TimeUnit unit) throws Exception {
return internalLock(time, unit);
} /**
* 释放锁
*/
public void release() throws Exception {
releaseLock(ourLockPath);
System.out.println(ourLockPath + "锁已释放...");
}
}

3. 分布式锁的实现细节
获取分布式锁的重点逻辑在于BaseDistributedLock,实现了基于Zookeeper实现分布式锁的细节。

package com.ljq.lock;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNoNodeException; public class BaseDistributedLock {
private final ZkClient client; //Zookeeper客户端
private final String basePath; //用于保存Zookeeper中实现分布式锁的节点,例如/locker节点,该节点是个持久节点,在该节点下面创建临时顺序节点来实现分布式锁
private final String path; //同basePath变量一样
private final String lockName; //锁名称前缀,/locker下创建的顺序节点,例如以lock-开头,这样便于过滤无关节点
private static final Integer MAX_RETRY_COUNT = 10; //最大重试次数 public BaseDistributedLock(ZkClient client, String path, String lockName) {
this.client = client;
this.basePath = path;
this.path = path.concat("/").concat(lockName);
this.lockName = lockName;
} /**
* 删除节点
*
* @param path
* @throws Exception
*/
private void deletePath(String path) throws Exception {
client.delete(path);
} /**
* 创建临时顺序节点
*
* @param client Zookeeper客户端
* @param path 节点路径
* @return
* @throws Exception
*/
private String createEphemeralSequential(ZkClient client, String path) throws Exception {
return client.createEphemeralSequential(path, null);
} /**
* 获取锁的核心方法
*
* @param startMillis 当前系统时间
* @param millisToWait 超时时间
* @param path
* @return
* @throws Exception
*/
private boolean waitToLock(long startMillis, Long millisToWait, String path) throws Exception {
boolean haveTheLock = false; //获取锁标志
boolean doDelete = false; //删除锁标志 try {
while (!haveTheLock) {
// 获取/locker节点下的所有顺序节点,并且从小到大排序
List<String> children = getSortedChildren();
// 获取子节点,如:/locker/node_0000000003返回node_0000000003
String sequenceNodeName = path.substring(basePath.length() + 1); // 计算刚才客户端创建的顺序节点在locker的所有子节点中排序位置,如果是排序为0,则表示获取到了锁
int ourIndex = children.indexOf(sequenceNodeName); /*
* 如果在getSortedChildren中没有找到之前创建的[临时]顺序节点,这表示可能由于网络闪断而导致
* Zookeeper认为连接断开而删除了我们创建的节点,此时需要抛出异常,让上一级去处理
* 上一级的做法是捕获该异常,并且执行重试指定的次数,见后面的 attemptLock方法
*/
if (ourIndex < 0) {
throw new ZkNoNodeException("节点没有找到: " + sequenceNodeName);
} // 如果当前客户端创建的节点在locker子节点列表中位置大于0,表示其它客户端已经获取了锁
// 此时当前客户端需要等待其它客户端释放锁
boolean isGetTheLock = ourIndex == 0; //是否得到锁 // 如何判断其它客户端是否已经释放了锁?从子节点列表中获取到比自己次小的那个节点,并对其建立监听
String pathToWatch = isGetTheLock ? null : children.get(ourIndex - 1); //获取比自己次小的那个节点,如:node_0000000002 if (isGetTheLock) {
haveTheLock = true;
} else {
// 如果次小的节点被删除了,则表示当前客户端的节点应该是最小的了,所以使用CountDownLatch来实现等待
String previousSequencePath = basePath.concat("/").concat(pathToWatch);
final CountDownLatch latch = new CountDownLatch(1);
final IZkDataListener previousListener = new IZkDataListener() {
/**
* 监听指定节点删除时触发该方法
*/
public void handleDataDeleted(String dataPath)
throws Exception {
// 次小节点删除事件发生时,让countDownLatch结束等待
// 此时还需要重新让程序回到while,重新判断一次!
latch.countDown();
} /**
* 监听指定节点的数据发生变化触发该方法
*
*/
public void handleDataChange(String dataPath,
Object data) throws Exception { } }; try {
// 如果节点不存在会出现异常
client.subscribeDataChanges(previousSequencePath, previousListener); //监听比自己次小的那个节点 //发生超时需要删除节点
if (millisToWait != null) {
millisToWait -= (System.currentTimeMillis() - startMillis);
startMillis = System.currentTimeMillis();
if (millisToWait <= 0) {
doDelete = true; // timed out - delete our node
break;
} latch.await(millisToWait, TimeUnit.MICROSECONDS);
} else {
latch.await();
} } catch (ZkNoNodeException e) {
// ignore
} finally {
client.unsubscribeDataChanges(previousSequencePath, previousListener);
}
}
}
} catch (Exception e) {
// 发生异常需要删除节点
doDelete = true;
throw e; } finally {
// 如果需要删除节点
if (doDelete) {
deletePath(path);
}
}
return haveTheLock;
} private String getLockNodeNumber(String str, String lockName) {
int index = str.lastIndexOf(lockName);
if (index >= 0) {
index += lockName.length();
return index <= str.length() ? str.substring(index) : "";
}
return str;
} /**
* 获取parentPath节点下的所有顺序节点,并且从小到大排序
*
* @return
* @throws Exception
*/
private List<String> getSortedChildren() throws Exception {
try {
List<String> children = client.getChildren(basePath);
Collections.sort(children, new Comparator<String>() {
public int compare(String lhs, String rhs) {
return getLockNodeNumber(lhs, lockName).compareTo(
getLockNodeNumber(rhs, lockName));
}
});
return children; } catch (ZkNoNodeException e) {
client.createPersistent(basePath, true); //创建锁持久节点
return getSortedChildren();
}
} /**
* 释放锁
*
* @param lockPath
* @throws Exception
*/
protected void releaseLock(String lockPath) throws Exception {
deletePath(lockPath);
} /**
* 尝试获取锁
*
* @param time
* @param unit
* @return
* @throws Exception
*/
protected String attemptLock(long time, TimeUnit unit) throws Exception {
final long startMillis = System.currentTimeMillis();
final Long millisToWait = (unit != null) ? unit.toMillis(time) : null; String ourPath = null;
boolean hasTheLock = false; //获取锁标志
boolean isDone = false; //是否完成得到锁
int retryCount = 0; //重试次数 // 网络闪断需要重试一试
while (!isDone) {
isDone = true; try {
// createLockNode用于在locker(basePath持久节点)下创建客户端要获取锁的[临时]顺序节点
ourPath = createEphemeralSequential(client, path);
/**
* 该方法用于判断自己是否获取到了锁,即自己创建的顺序节点在locker的所有子节点中是否最小
* 如果没有获取到锁,则等待其它客户端锁的释放,并且稍后重试直到获取到锁或者超时
*/
hasTheLock = waitToLock(startMillis, millisToWait, ourPath); } catch (ZkNoNodeException e) {
if (retryCount++ < MAX_RETRY_COUNT) {
isDone = false;
} else {
throw e;
}
}
} System.out.println(ourPath + "锁获取" + (hasTheLock ? "成功" : "失败"));
if (hasTheLock) {
return ourPath;
} return null;
}
}

4. 获取锁调用demo

package com.ljq.lock;

import org.I0Itec.zkclient.ZkClient;

public class LockTest {
public static void main(String[] args) throws Exception {
ZkClient zkClient = new ZkClient("192.168.2.249:2181", 3000);
SimpleDistributedLock simple = new SimpleDistributedLock(zkClient, "/locker"); for (int i = 0; i < 10; i++) {
try {
simple.acquire();
System.out.println("正在进行运算操作:" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
} finally {
simple.release();
System.out.println("=================\r\n");
}
}
}
}

5. 获取锁控制台信息

/locker/lock-0000000131锁获取成功
正在进行运算操作:
/locker/lock-0000000131锁已释放...
================= /locker/lock-0000000132锁获取成功
正在进行运算操作:
/locker/lock-0000000132锁已释放...
================= /locker/lock-0000000133锁获取成功
正在进行运算操作:
/locker/lock-0000000133锁已释放...
================= /locker/lock-0000000134锁获取成功
正在进行运算操作:
/locker/lock-0000000134锁已释放...
================= /locker/lock-0000000135锁获取成功
正在进行运算操作:
/locker/lock-0000000135锁已释放...
================= /locker/lock-0000000136锁获取成功
正在进行运算操作:
/locker/lock-0000000136锁已释放...
================= /locker/lock-0000000137锁获取成功
正在进行运算操作:
/locker/lock-0000000137锁已释放...
================= /locker/lock-0000000138锁获取成功
正在进行运算操作:
/locker/lock-0000000138锁已释放...
================= /locker/lock-0000000139锁获取成功
正在进行运算操作:
/locker/lock-0000000139锁已释放...
================= /locker/lock-0000000140锁获取成功
正在进行运算操作:
/locker/lock-0000000140锁已释放...
=================

zookeeper分布式锁实现的更多相关文章

  1. Curator Zookeeper分布式锁

    Curator Zookeeper分布式锁 pom.xml中添加如下配置 <!-- https://mvnrepository.com/artifact/org.apache.curator/c ...

  2. ZooKeeper 分布式锁实现

    1 场景描述 在分布式应用, 往往存在多个进程提供同一服务. 这些进程有可能在相同的机器上, 也有可能分布在不同的机器上. 如果这些进程共享了一些资源, 可能就需要分布式锁来锁定对这些资源的访问. 2 ...

  3. ZooKeeper分布式锁浅谈(一)

    一.概述 清明节的时候写了一篇分布式锁概述,里面介绍了分布式锁实现的几种方式,其实那时候我一直沉迷于使用redis的悲观锁和乐观锁来实现分布式锁,直到一个血案的引发才让我重新认识了redis分布式锁的 ...

  4. [转载] zookeeper 分布式锁服务

    转载自http://www.cnblogs.com/shanyou/archive/2012/09/22/2697818.html 分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那 ...

  5. 跟着大神学zookeeper分布式锁实现-----来自Ruthless

    前几天分享了@Ruthless大神的Redis锁,发现和大家都学习了很多东西.因为分布式锁里面,最好的实现是zookeeper的分布式锁.所以在这里把实现方式和大家分享一下. zookeeper分布式 ...

  6. zookeeper分布式锁

    摘要:分享牛原创,zookeeper使用,zookeeper锁在实际项目开发中还是很常用的,在这里我们介绍一下zookeeper分布式锁的使用,以及我们如何zookeeper分布式锁的原理.zooke ...

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

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  8. zookeeper 分布式锁原理

    zookeeper 分布式锁原理: 1 大家也许都很熟悉了多个线程或者多个进程间的共享锁的实现方式了,但是在分布式场景中我们会面临多个Server之间的锁的问题,实现的复杂度比较高.利用基于googl ...

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

    什么是Zookeeper? Zookeeper(业界简称zk)是一种提供配置管理.分布式协同以及命名的中心化服务,这些提供的功能都是分布式系统中非常底层且必不可少的基本功能,但是如果自己实现这些功能而 ...

  10. ZooKeeper 分布式锁

    在Redis分布式锁一文中, 作者介绍了如何使用Redis开发分布式锁. Redis分布式锁具有轻量高吞吐量的特点,但是一致性保证较弱.我们可以使用Zookeeper开发分布式锁,来满足对高一致性的要 ...

随机推荐

  1. BZOJ 1176 [Balkan2007]Mokia ——CDQ分治

    [题目分析] 同BZOJ2683,只需要提前处理s对结果的影响即可. CDQ的思路还是很清晰的. 排序解决一维, 分治时间, 树状数组解决一维. 复杂度是两个log [代码] #include < ...

  2. Java基础加强之多线程篇(线程创建与终止、互斥、通信、本地变量)

    线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...

  3. 【字符串匹配】KMP算法和next数组的c/c++实现

    KMP算法基本思想有许多博客都写到了,写得也十分形象,不懂得可以参考下面的传送门,我就不解释基本思想了.本文主要给出KMP算法及next数组的计算方法(主要是很多网上的代码本人(相信应该是许多人吧)看 ...

  4. 织梦cms更新新建的栏目提示:DedeTag Engine Create File False:C:/phpStudy/WWW/

    这个问题怎么解决呢?只是更换了一下栏目名称.增加了新的栏目,结果就不行了. 问题原因: 新建的栏目没有"列表命名规则",导致cms解析的时候,出现解析错误. 解决办法 在下图填入: ...

  5. spring源码解析——spring源码导入eclipse

    一.前言     众所周知,spring的强大之处.几乎所有的企业级开发中,都使用了spring了.在日常的开发中,我们是否只知道spring的配置,以及简单的使用场景.对其实现的代码没有进行深入的了 ...

  6. 使用js-xlsx库,前端读取Excel报表文件

    在实际开发中,经常会遇到导入Excel文件的需求,有的产品人想法更多,想要在前端直接判断文件内容格式是否正确,必填项是否已填写 依据HTML5的FileReader,可以使用新的API打开本地文件(参 ...

  7. PHP获取二维数组中的指定若干列【同array_column】

    PHP5.3以上  用到了array_map 使用匿名函数进行处理 代码: <?php function array_col($arr = array(), $idx = 0, $newidx ...

  8. java分享第十九天(TestNg的IReporter接口的使用)

     IReporter接口是干嘛的?就是让用户自定义报告的,很多人想要自定义报告,于是乎找各种插件,比如什么testng-xslt啊,reportng啊,各种配置,最后出来的结果,还不能定制化,但为什么 ...

  9. Where product development should start

    We all need to know our customers in order to create products they’ll actually buy. This is why the  ...

  10. 线程处理模型 由于 SynchronizationContext 引起的死锁问题解决

    由于GUI 应用程序 不能使用线程池的线程更新UI,只能使用 GUI 线程更新,所以在 await 前后需要保证是同一个 GUI 线程 ASP.NET 程序 的线程处理客户端请求的时候,需要假定客户端 ...