实现思路

公平锁:创建有序节点,判断本节点是不是序号最小的节点(第一个节点),若是,则获取锁;若不是,则监听比该节点小的那个节点的删除事件。

非公平锁:直接尝试在指定path下创建节点,创建成功,则说明该节点抢到锁了。如果创建失败,则监听锁节点的删除事件,或者sleep一段时间后再重试。

可重入:使用ThreadLocal记录加锁客户端的唯一标识。重复时先从ThreadLocal获取,获取到,这认为加锁成功,直接返回。

使用瞬时节点创建可重入公平锁

使用瞬时节点的好处是当Session失效时,该节点将被清理,从而避免使用持久节点加锁成功后,抛异常或宕机或服务器重启等原因造成的锁无法释放问题。

使用curator实现

// 假设需要加锁的订单Id
private static String orderId = "157146671409578219";
// 工程名
private static String appName = "trade_";
// 此次加锁业务处理逻辑描述
private static String operatorDesc = "updateTrade_"; // 部门,每个部门可以有自己的zk空间目录
private static String department = "zfpt" ; // 锁前缀 应该根据业务 具有唯一性
private static String lockPrefixKey = "/" + appName + operatorDesc ; /**
* 每个线程 创建自己的Connection ,创建自己的Session
*/
@Test
public void curator() throws Exception { for (int i = 0 ; i < 100 ; i++) { Thread currentThread = new Thread(() -> { // 创建Connection
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("master:2181,slave1:2181,slave2:2181")
.retryPolicy(new RetryOneTime(1000)) //重试策略
.namespace(department) // 可以设置自己部门缩写
.build();
client.start(); // 模拟对同一个订单加锁
InterProcessMutex lock = new InterProcessMutex(client, lockPrefixKey + orderId); try {
// 一直尝试加锁 直到锁可用。 有点像synchronized
// lock.acquire();
if(lock.acquire(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " 抢到锁");
} else {
System.out.println(Thread.currentThread().getName() + "超时没有抢到锁");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 如果当前线程获得到锁,则释放锁
if(lock.isAcquiredInThisProcess()) {
System.out.println(Thread.currentThread().getName() + " 释放锁");
lock.release();
} else {
System.out.println(Thread.currentThread().getName() + " 没有抢到锁,故没有释放锁");
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
currentThread.setName("Lock【" + i + "】");
currentThread.start();
} Thread.sleep(Integer.MAX_VALUE);
}

实现原理:

因为ZK不允许在临时节点下创建子节点,所以InterProcessMutex工具类会根据加锁传入path的(也就是案例中的lockPrefixKey + orderId)创建一个持久节点;然后在这个持久节点下创建瞬时节点,创建成功后,对该持久节点下的全部子节点进行降序排序,判断当前节点是否是第一个节点,如果是则获取锁,否则对前一个节点加上监听事件。然后Object.wait(),当监听事件被触发,则会调用notifyAll方法,对等待线程进行唤醒。再次尝试获取锁。

缺点:

  1. 传入的加锁节点会被创建为永久节点(就是lockPrefixKey + orderId),这样zk节点数量将会急速递增。
  2. 临时节点不稳定:一个客户端加锁成功后,可能会因为网络抖动等原因导致Session断开,该客户端创建的临时节点被清理,导致另外一节正在监听的客户端加锁成功,同时进行操作。

使用持久节点创建可重入公平锁

上文提到的临时节点不稳定,父节点为永久节点无法释放问题。我的拙见是:

  1. 使用持久节点代替临时节点:释放锁的时候删除自己创建的加锁节点。
  2. 父节点为永久节点无法释放:可以在每个客户端释放锁的时候进行判断,如果自己是最后一个节点,则删除父节点。
  3. 但是需要考虑的问题:客户端加锁成功后,宕机或重启或者其他极端异常情况,无法删除加锁节点。最后一个加锁节点同样异常,也无法删除父节点。这时,可以给每个锁加过期时间,过期失效。由于zk没有提供过期自动清理,所以可以在第二次访问该节点的时候 先进行判断,判断失效先删除再创建。如果没有第二次访问的节点 可以依靠定时任务进行节点清理。

ZK分布式锁(未完 待续)的更多相关文章

  1. Go web编程学习笔记——未完待续

    1. 1).GOPATH设置 先设置自己的GOPATH,可以在本机中运行$PATH进行查看: userdeMacBook-Pro:~ user$ $GOPATH -bash: /Users/user/ ...

  2. 本地锁、redis分布式锁、zk分布式锁

    本地锁.redis分布式锁.zk分布式锁 https://www.cnblogs.com/yjq-code/p/dotnetlock.html 为什么要用锁? 大型站点在高并发的情况下,为了保持数据最 ...

  3. 【分布式锁的演化】终章!手撸ZK分布式锁!

    前言 这应该是分布式锁演化的最后一个章节了,相信很多小伙伴们看完这个章节之后在应对高并发的情况下,如何保证线程安全心里肯定也会有谱了.在实际的项目中也可以参考一下老猫的github上的例子,当然代码没 ...

  4. git安装与使用,未完待续... ...

    ​ 目录 一.git概念 二.git简史 三.git的安装 四.git结构 五.代码托管中心-本地库和远程库的交互方式 六.初始化本地仓库 七.git常用命令 1.add和commit命令 2.sta ...

  5. javascript有用小功能总结(未完待续)

    1)javascript让页面标题滚动效果 代码如下: <title>您好,欢迎访问我的博客</title> <script type="text/javasc ...

  6. ASP.NET MVC 系列随笔汇总[未完待续……]

    ASP.NET MVC 系列随笔汇总[未完待续……] 为了方便大家浏览所以整理一下,有的系列篇幅中不是很全面以后会慢慢的补全的. 学前篇之: ASP.NET MVC学前篇之扩展方法.链式编程 ASP. ...

  7. 关于DOM的一些总结(未完待续......)

    DOM 实例1:购物车实例(数量,小计和总计的变化) 这里主要是如何获取页面元素的节点: document.getElementById("...") cocument.query ...

  8. 我的SQL总结---未完待续

    我的SQL总结---未完待续 版权声明:本文为博主原创文章,未经博主允许不得转载. 总结: 主要的SQL 语句: 数据操作(select, insert, delete, update) 访问控制(g ...

  9. virtualbox搭建ubuntu server nginx+mysql+tomcat web服务器1 (未完待续)

    virtualbox搭建ubuntu server nginx+mysql+tomcat web服务器1 (未完待续) 第一次接触到 linux,不知道linux的确很强大,然后用virtualbox ...

  10. MVC丶 (未完待续······)

         希望你看了此小随 可以实现自己的MVC框架     也祝所有的程序员身体健康一切安好                                                     ...

随机推荐

  1. 解题:USACO12OPEN Bookshelf

    题面 从零开始的DP学习之肆 当DP方程中的一部分具有某种单调性时可以用数据结构或者预处理维护来降低复杂度 一开始没有看懂题,尴尬,后来发现题目可以简化成这个样子: 将一个序列划分为若干段,每段长度不 ...

  2. 阿里云 邮件发送(Python)

    #coding:utf8 from smtplib import SMTP_SSL from email.header import Header from email.mime.text impor ...

  3. Turn Off The Light HDU - 6307

    题目大意 是有n个位置有灯,告诉你每个位置灯是开着的还是关着的,告诉你你的初始位置p,你可以往左或者右移动一步(在1到n的范围里移动), 并且在移动后必须按下开关(就是使当前打开的灯关上,当前未打开的 ...

  4. Chapter 6(树)

    1.树的储存方式 //****************双亲表示法************************ #define Max_TREE_SIZE 100 typedef int TElem ...

  5. HDU 3943 数位dp+二分

    K-th Nya Number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 125536/65536 K (Java/Others) ...

  6. K8S调度之节点亲和性

    Node Affinity Affinity 翻译成中文是"亲和性",它对应的是 Anti-Affinity,我们翻译成"互斥".这两个词比较形象,可以把 po ...

  7. day4 数组学习

    java提供的数组排序操作:java.util.Arrays.sort(数组名): java提供的数组复制:system.arraycopy(源数组名称,源数组开始点下标,目标数组名称,目标数组开始下 ...

  8. UIScrollView的contentSize与contentOffset

    UIScrollView为了显示多于一个屏幕的内容或者超过你能放在内存中的内容. Scroll View为你处理缩小放大手势,UIScrollView实现了这些手势,并且替你处理对于它们的探测和回应. ...

  9. snprintf()解析

    snprintf(ssid_mac,sizeof(ssid_mac),"%s_%02X%02X",ssid,macval[4],macval[5]); ssid_mac = ssi ...

  10. Android 利用 gson 将 json 转成 对象object 集合list

    1.build.gradle 中引入gson compile 'com.google.code.gson:gson:2.8.5' 2.将后台返回的json数据转对象.List.时间格式与后台返回的时间 ...