ZooKeeper分布式锁的实现
ZooKeeper分布式锁的实现。
在分布式的情况下,sychornized 和 Lock 已经不能满足我们的要求了,那么就需要使用第三方的锁了,这里我们就使用 ZooKeeper 来实现一个分布式锁。
一、分布式锁方案比较
方案 | 实现思路 | 优点 | 缺点 |
---|---|---|---|
利用 MySQL 的实现方案 | 利用数据库自身提供的锁机制实现,要求数据库支持行级锁 | 实现简单 | 性能差,无法适应高并发场景;容易出现死锁的情况;无法优雅的实现阻塞式锁 |
利用 Redis 的实现方案 | 使用 Setnx 和 lua 脚本机制实现,保证对缓存操作序列的原子性 | 性能好 | 实现相对复杂,有可能出现死锁;无法优雅的实现阻塞式锁 |
利用 ZooKeeper 的实现方案 | 基于 ZooKeeper 节点特性及 watch 机制实现 | 性能好,稳定可靠性高,能较好地实现阻塞式锁 | 实现相对复杂 |
二、ZooKeeper实现分布式锁
这里使用 ZooKeeper 来实现分布式锁,以50个并发请求来获取订单编号为例,描述两种方案,第一种为基础实现,第二种在第一种基础上进行了优化。
1. 方案一
流程描述:
具体代码:
OrderNumGenerator:
/**
* @Author SunnyBear
* @Description 生成随机订单号
*/
public class OrderNumGenerator {
private static long count = 0;
/**
* 使用日期加数值拼接成订单号
*/
public String getOrderNumber() throws Exception {
String date = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
String number = new DecimalFormat("000000").format(count++);
return date + number;
}
}
Lock:
/**
* @Author SunnyBear
* @Description 自定义锁接口
*/
public interface Lock {
/**
* 获取锁
*/
public void getLock();
/**
* 释放锁
*/
public void unLock();
}
AbstractLock:
/**
* @Author SunnyBear
* @Description 定义一个模板,具体的方法由子类来实现
*/
public abstract class AbstractLock implements Lock {
/**
* 获取锁
*/
@Override
public void getLock() {
if (tryLock()) {
System.out.println("--------获取到了自定义Lock锁的资源--------");
} else {
// 没拿到锁则阻塞,等待拿锁
waitLock();
getLock();
}
}
/**
* 尝试获取锁,如果拿到了锁返回true,没有拿到则返回false
*/
public abstract boolean tryLock();
/**
* 阻塞,等待获取锁
*/
public abstract void waitLock();
}
ZooKeeperAbstractLock:
/**
* @Author SunnyBear
* @Description 定义需要的服务连接
*/
public abstract class ZooKeeperAbstractLock extends AbstractLock {
private static final String SERVER_ADDR = "192.168.182.130:2181,192.168.182.131:2181,192.168.182.132:2181";
protected ZkClient zkClient = new ZkClient(SERVER_ADDR);
protected static final String PATH = "/lock";
}
ZooKeeperDistrbuteLock:
/**
* @Author SunnyBear
* @Description 真正实现锁的细节
*/
public class ZooKeeperDistrbuteLock extends ZooKeeperAbstractLock {
private CountDownLatch countDownLatch = null;
/**
* 尝试拿锁
*/
@Override
public boolean tryLock() {
try {
// 创建临时节点
zkClient.createEphemeral(PATH);
return true;
} catch (Exception e) {
// 创建失败报异常
return false;
}
}
/**
* 阻塞,等待获取锁
*/
@Override
public void waitLock() {
// 创建监听
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();
}
}
};
// 注册监听
zkClient.subscribeDataChanges(PATH, iZkDataListener);
// 节点存在时,等待节点删除唤醒
if (zkClient.exists(PATH)) {
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 删除监听
zkClient.unsubscribeDataChanges(PATH, iZkDataListener);
}
/**
* 释放锁
*/
@Override
public void unLock() {
if (zkClient != null) {
System.out.println("释放锁资源");
zkClient.delete(PATH);
zkClient.close();
}
}
}
测试效果:使用50个线程来并发测试ZooKeeper实现的分布式锁
/**
* @Author SunnyBear
* @Description 使用50个线程来并发测试ZooKeeper实现的分布式锁
*/
public class OrderService {
private static class OrderNumGeneratorService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();;
private Lock lock = new ZooKeeperDistrbuteLock();
@Override
public void run() {
lock.getLock();
try {
System.out.println(Thread.currentThread().getName() + ", 生成订单编号:" + orderNumGenerator.getOrderNumber());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unLock();
}
}
}
public static void main(String[] args) {
System.out.println("----------生成唯一订单号----------");
for (int i = 0; i < 50; i++) {
new Thread(new OrderNumGeneratorService()).start();
}
}
}
2. 方案二
方案二在方案一的基础上进行优化,避免产生“羊群效应”,方案一一旦临时节点删除,释放锁,那么其他在监听这个节点变化的线程,就会去竞争锁,同时访问 ZooKeeper,那么怎么更好的避免各线程的竞争现象呢,就是使用临时顺序节点,临时顺序节点排序,每个临时顺序节点只监听它本身的前一个节点变化。
流程描述:
具体代码
具体只需要将方案一中的 ZooKeeperDistrbuteLock 改变,增加一个 ZooKeeperDistrbuteLock2,测试代码中使用 ZooKeeperDistrbuteLock2 即可测试,其他代码都不需要改变。
/**
* @Author SunnyBear
* @Description 真正实现锁的细节
*/
public class ZooKeeperDistrbuteLock2 extends ZooKeeperAbstractLock {
private CountDownLatch countDownLatch = null;
/**
* 当前请求节点的前一个节点
*/
private String beforePath;
/**
* 当前请求的节点
*/
private String currentPath;
public ZooKeeperDistrbuteLock2() {
if (!zkClient.exists(PATH)) {
// 创建持久节点,保存临时顺序节点
zkClient.createPersistent(PATH);
}
}
@Override
public boolean tryLock() {
// 如果currentPath为空则为第一次尝试拿锁,第一次拿锁赋值currentPath
if (currentPath == null || currentPath.length() == 0) {
// 在指定的持久节点下创建临时顺序节点
currentPath = zkClient.createEphemeralSequential(PATH + "/", "lock");
}
// 获取所有临时节点并排序,例如:000044
List<String> childrenList = zkClient.getChildren(PATH);
Collections.sort(childrenList);
if (currentPath.equals(PATH + "/" + childrenList.get(0))) {
// 如果当前节点在所有节点中排名第一则获取锁成功
return true;
} else {
int wz = Collections.binarySearch(childrenList, currentPath.substring(6));
beforePath = PATH + "/" + childrenList.get(wz - 1);
}
return false;
}
@Override
public void waitLock() {
// 创建监听
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();
}
}
};
// 注册监听,这里是给排在当前节点前面的节点增加(删除数据的)监听,本质是启动另外一个线程去监听前置节点
zkClient.subscribeDataChanges(beforePath, iZkDataListener);
// 前置节点存在时,等待前置节点删除唤醒
if (zkClient.exists(beforePath)) {
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 删除对前置节点的监听
zkClient.unsubscribeDataChanges(beforePath, iZkDataListener);
}
/**
* 释放锁
*/
@Override
public void unLock() {
if (zkClient != null) {
System.out.println("释放锁资源");
zkClient.delete(currentPath);
zkClient.close();
}
}
}
都读到这里了,来个 点赞、评论、关注、收藏 吧!
文章作者:IT王小二
首发地址:https://www.itwxe.com/posts/6d9ff3d6/
版权声明:文章内容遵循 署名-非商业性使用-禁止演绎 4.0 国际 进行许可,转载请在文章页面明显位置给出作者与原文链接。
ZooKeeper分布式锁的实现的更多相关文章
- Curator Zookeeper分布式锁
Curator Zookeeper分布式锁 pom.xml中添加如下配置 <!-- https://mvnrepository.com/artifact/org.apache.curator/c ...
- ZooKeeper 分布式锁实现
1 场景描述 在分布式应用, 往往存在多个进程提供同一服务. 这些进程有可能在相同的机器上, 也有可能分布在不同的机器上. 如果这些进程共享了一些资源, 可能就需要分布式锁来锁定对这些资源的访问. 2 ...
- ZooKeeper分布式锁浅谈(一)
一.概述 清明节的时候写了一篇分布式锁概述,里面介绍了分布式锁实现的几种方式,其实那时候我一直沉迷于使用redis的悲观锁和乐观锁来实现分布式锁,直到一个血案的引发才让我重新认识了redis分布式锁的 ...
- [转载] zookeeper 分布式锁服务
转载自http://www.cnblogs.com/shanyou/archive/2012/09/22/2697818.html 分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那 ...
- 跟着大神学zookeeper分布式锁实现-----来自Ruthless
前几天分享了@Ruthless大神的Redis锁,发现和大家都学习了很多东西.因为分布式锁里面,最好的实现是zookeeper的分布式锁.所以在这里把实现方式和大家分享一下. zookeeper分布式 ...
- zookeeper分布式锁
摘要:分享牛原创,zookeeper使用,zookeeper锁在实际项目开发中还是很常用的,在这里我们介绍一下zookeeper分布式锁的使用,以及我们如何zookeeper分布式锁的原理.zooke ...
- 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...
- zookeeper 分布式锁原理
zookeeper 分布式锁原理: 1 大家也许都很熟悉了多个线程或者多个进程间的共享锁的实现方式了,但是在分布式场景中我们会面临多个Server之间的锁的问题,实现的复杂度比较高.利用基于googl ...
- 分布式锁(一) Zookeeper分布式锁
什么是Zookeeper? Zookeeper(业界简称zk)是一种提供配置管理.分布式协同以及命名的中心化服务,这些提供的功能都是分布式系统中非常底层且必不可少的基本功能,但是如果自己实现这些功能而 ...
- ZooKeeper 分布式锁
在Redis分布式锁一文中, 作者介绍了如何使用Redis开发分布式锁. Redis分布式锁具有轻量高吞吐量的特点,但是一致性保证较弱.我们可以使用Zookeeper开发分布式锁,来满足对高一致性的要 ...
随机推荐
- "mysql第一次查询很慢,以后就很快"的解决方案
背景 有个项目使用的mysql数据库,第一次查询很慢,大约15s左右出结果,再次查询就很快了. 分析 后面变快的原因是mysql有缓存机制,但是过上一段时间不使用缓存会过期,我个人测了一下2~3分钟一 ...
- 微服务·API文档
阅文时长 | 3.92分钟 字数统计 | 2754.05字符 主要内容 | 1.什么是API文档 2.API文档的使用 3.声明与参考资料 『微服务·API文档』 编写人 | SCscHero 编写时 ...
- ES6学习-5 解构赋值(2)对象的解构赋值
啥也不说,先举个栗子: 1 let { myname, myage } = { myage: 18, myname: "郭郭" }; 2 console.log(myname) / ...
- [刷题] 102 Binary Tree Level Order Traversal
要求 对二叉树进行层序遍历 实现 返回结果为双重向量,对应树的每层元素 队列的每个元素是一个pair对,存树节点和其所在的层信息 1 Definition for a binary tree node ...
- 强哥CSS学习笔记
html嵌套css样式:1.外部(推荐)2.内部3.内联(不推荐) css优先级1.内联2.id选择器3.class选择器4.标签 css长度单位:1.px2.em (14px) css选择器:常用选 ...
- 联想 lenove 3750 M4服务器更改启动项和管理口IP
联想 lenove 3750 M4服务器更改启动项和管理口IP 注: 因为在机房拍照的原因,再加上工作比较忙:整理成文档的时候有的过程已经忘记了,所以有的步骤可能会缺失,里面的选项都已经用中文方式表达 ...
- 如何做好FAE工作及FAE职位发展————资深FAE总结推荐
http://bbs.elecfans.com/jishu_932585_1_1.html 曾经认识一位做电源研发的工程师,转行在一家代理商做FAE,做了一年半以后,就提出了离职请求,他老板问他是什么 ...
- Chrome版本与chromedriver版本映射表
chromedriver版本 支持的Chrome版本 v2.36 v64-66 v2.35 v62-64 v2.34 v61-63 v2.33 v60-62 v2.32 v59-61 v2.31 v5 ...
- Docker的镜像理解以及容器的备份、恢复和迁移操作
Docker的镜像理解以及容器的备份.恢复和迁移操作 这篇文章主要介绍了Docker的镜像理解以及容器的备份.恢复和迁移操作,覆盖很多原理知识以及容器的重要操作步骤,极力推荐!需要的朋友可以参考下 D ...
- C#中的数据结构
Array 连续性的内存空间 快速定位查找元素,随机访问性强,查找效率高 已知的统一的元素类型,减小运行时开销 固定长度,不能再新增元素 ArrayList 连续性的内存空间 快速定位查找元素,随机访 ...