分析ZooKeeper分布式锁的实现
一、分布式锁方案比较
方案 | 实现思路 | 优点 | 缺点 |
---|---|---|---|
利用 MySQL 的实现方案 | 利用数据库自身提供的锁机制实现,要求数据库支持行级锁 | 实现简单 | 性能差,无法适应高并发场景;容易出现死锁的情况;无法优雅的实现阻塞式锁 |
利用 Redis 的实现方案 | 使用 Setnx 和 lua 脚本机制实现,保证对缓存操作序列的原子性 | 性能好 | 实现相对复杂,有可能出现死锁;无法优雅的实现阻塞式锁 |
利用 ZooKeeper 的实现方案 | 基于 ZooKeeper 节点特性及 watch 机制实现 | 性能好,稳定可靠性高,能较好地实现阻塞式锁 | 实现相对复杂 |
二、ZooKeeper实现分布式锁
这里使用 ZooKeeper 来实现分布式锁,以50个并发请求来获取订单编号为例,描述两种方案,第一种为基础实现,第二种在第一种基础上进行了优化。
2.1、方案一
流程描述:
具体代码:
OrderNumGenerator:
/** * @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:
/** * @Description 自定义锁接口 */ public interface Lock { /** * 获取锁 */ public void getLock(); /** * 释放锁 */ public void unLock(); }
AbstractLock:
/** * @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:
/** * @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:
/** * @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实现的分布式锁
/** * @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.2、方案二
方案二在方案一的基础上进行优化,避免产生“羊群效应”,方案一一旦临时节点删除,释放锁,那么其他在监听这个节点变化的线程,就会去竞争锁,同时访问 ZooKeeper,那么怎么更好的避免各线程的竞争现象呢,就是使用临时顺序节点,临时顺序节点排序,每个临时顺序节点只监听它本身的前一个节点变化。
流程描述:
具体代码
具体只需要将方案一中的 ZooKeeperDistrbuteLock 改变,增加一个 ZooKeeperDistrbuteLock2,测试代码中使用 ZooKeeperDistrbuteLock2 即可测试,其他代码都不需要改变。
/** * @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(); } } }
以上就是分析ZooKeeper分布式锁的实现的详细内容,更多关于ZooKeeper分布式锁的资料请关注猪先飞其它相关文章!
相关文章
- 由于 ZooKeeper 便捷的使用方式、卓越的性能和良好的稳定性,被广泛地应用于诸如 Hadoop、HBase、Kafka 和 Dubbo 等大型分布式系统中。这篇文章主要介绍了史上最便捷搭建Zookeeper服务器的方法,需要的朋友可以参考下...2021-05-07
- 这篇文章主要介绍了SpringBoot集成redis实现分布式锁的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-25
- Zookeeper是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护、名字服务、分布式同步、组服务等,这篇文章主要介绍了ZooKeeper的安装及部署,需要的朋友可以参考下...2020-06-25
- 这篇文章主要介绍了redission分布式锁防止重复初始化问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-15
详解redis分布式锁(优化redis分布式锁的过程及Redisson使用)
在分布式的开发中,以电商库存的更新功能进行讲解,在实际的应用中相同功能的消费者是有多个的,这篇文章主要介绍了redis分布式锁详解(优化redis分布式锁的过程及Redisson使用),需要的朋友可以参考下...2021-11-12- 这篇文章主要介绍了在Java中操作Zookeeper的示例代码详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-20
关于idea+centos7+zookeeper报错connectionloss,timeout问题
这篇文章主要介绍了idea+centos7+zookeeper报错connectionloss,timeout问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-19- 这篇文章主要为大家详细介绍了SpringBoot使用Redis实现分布式锁,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-05-17
- 这篇文章主要给大家介绍了关于Redis分布式锁的正确实现方法,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2021-01-15
- 在分布式的情况下,sychornized 和 Lock 已经不能满足我们的要求了,那么就需要使用第三方的锁了,这里我们就使用 ZooKeeper 来实现一个分布式锁...2021-07-01
SpringBoot系列教程之dubbo和Zookeeper集成方法
这篇文章主要介绍了SpringBoot系列教程之dubbo和Zookeeper集成方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-09-05- 本文主要介绍了Redis构建分布式锁的相关知识。具有很好的参考价值,下面跟着小编一起来看下吧...2017-04-03
- 这篇文章主要介绍了解决java连接zookeeper很慢的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-11-11
- 这篇文章主要给大家介绍了关于redis分布式锁及会出现问题的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-15
Redis分布式锁升级版RedLock及SpringBoot实现方法
这篇文章主要介绍了Redis分布式锁升级版RedLock及SpringBoot实现,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-02-01详解RedisTemplate下Redis分布式锁引发的系列问题
这篇文章主要介绍了详解RedisTemplate下Redis分布式锁引发的系列问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-24- 这篇文章主要介绍了Java工作中常见的并发问题处理方法总结,文章内容讲解的很清晰,有不太懂得同学可以跟着学习下...2021-02-10
- 这篇文章主要给大家介绍了关于Redis分布式锁的使用和实现原理的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-15
- 在本篇文章里小编给大家整理的是关于Redis分布式锁的正确实现方式介绍,有兴趣的朋友们可以学习下。...2021-01-15
- 这篇文章主要介绍了如何利用Redis分布式锁实现控制并发操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-15