点击上方“方志朋”,选择“设为星标”

回复”666“获取新整理的面试文章

zookeeper客户端选型

  • 原生zookeeper客户端,有watcher一次性、无超时重连机制等一系列问题

  • ZkClient,解决了原生客户端一些问题,一些存量老系统中还在使用

  • curator,提供了各种应用场景(封装了分布式锁,计数器等),新项目首选

分布式锁使用场景

在单体项目中jvm中的锁即可完成需要,但是微服务、分布式环境下,同一个服务可能部署在多台服务器上,多个jvm之间无法通过常用的jvm锁来完成同步操作,需要借用分布式锁来完成上锁、释放锁。例如在订单服务中,我们需要根据日期来生成订单号流水,就有可能产生相同的时间日期,从而出现重复订单号。

zookeeper分布式锁实现原理

  • zookeeper中规定,在同一时刻,不能有多个客户端创建同一个节点,我们可以利用这个特性实现分布式锁。zookeeper临时节点只在session生命周期存在,session一结束会自动销毁。

  • watcher机制,在代表锁资源的节点被删除,即可以触发watcher解除阻塞重新去获取锁,这也是zookeeper分布式锁较其他分布式锁方案的一大优势。

基于临时节点方案

第一种方案实现较为简单,逻辑就是谁创建成功该节点,谁就持有锁,创建失败的自己进行阻塞,A线程先持有锁,B线程获取失败就会阻塞,同时对/lockPath设置监听,A线程执行完操作后删除节点,触发监听器,B线程此时解除阻塞,重新去获取锁。

临时节点

我们模仿原生jdk的lock接口设计,采用模板方法设计模式来编写分布式锁,这样的好处是扩展性强,我们可以快速切换到redis分布式锁、数据库分布式锁等实现方式。

创建Lock接口

public interface Lock {/*** 获取锁*/void getLock() throws Exception;/*** 释放锁*/void unlock() throws Exception;
}

AbstractTemplateLock抽象类

public abstract class AbstractTemplateLock implements Lock {@Overridepublic void getLock() {if (tryLock()) {System.out.println(Thread.currentThread().getName() + "获取锁成功");} else {//等待waitLock();//事件监听 如果节点被删除则可以重新获取//重新获取getLock();}}protected abstract void waitLock();protected abstract boolean tryLock();protected abstract void releaseLock();@Overridepublic void unlock() {releaseLock();}
}

zookeeper分布式锁逻辑

@Slf4j
public class ZkTemplateLock extends AbstractTemplateLock {private static final String zkServers = "127.0.0.1:2181";private static final int sessionTimeout = 8000;private static final int connectionTimeout = 5000;private static final String lockPath = "/lockPath";private ZkClient client;public ZkTemplateLock() {client = new ZkClient(zkServers, sessionTimeout, connectionTimeout);log.info("zk client 连接成功:{}",zkServers);}@Overrideprotected void waitLock() {CountDownLatch latch = new CountDownLatch(1);IZkDataListener listener = new IZkDataListener() {@Overridepublic void handleDataDeleted(String dataPath) throws Exception {System.out.println("监听到节点被删除");latch.countDown();}@Overridepublic void handleDataChange(String dataPath, Object data) throws Exception {}};//完成 watcher 注册client.subscribeDataChanges(lockPath, listener);//阻塞自己if (client.exists(lockPath)) {try {latch.await();} catch (InterruptedException e) {e.printStackTrace();}}//取消watcher注册client.unsubscribeDataChanges(lockPath, listener);}@Overrideprotected boolean tryLock() {try {client.createEphemeral(lockPath);System.out.println(Thread.currentThread().getName()+"获取到锁");} catch (Exception e) {log.error("创建失败");return false;}return true;}@Overridepublic void releaseLock() {client.delete(this.lockPath);}
}

缺点

每次去竞争锁,都只会有一个线程拿到锁,当线程数庞大时会发生“惊群”现象,zookeeper节点可能会运行缓慢甚至宕机。这是因为其他线程没获取到锁时都会监听/lockPath节点,当A线程释放完毕,海量的线程都同时停止阻塞,去争抢锁,这种操作十分耗费资源,且性能大打折扣。

基于临时顺序节点方案

临时顺序节点与临时节点不同的是产生的节点是有序的,我们可以利用这一特点,只让当前线程监听上一序号的线程,每次获取锁的时候判断自己的序号是否为最小,最小即获取到锁,执行完毕就删除当前节点继续判断谁为最小序号的节点。

序列化节点

临时顺序节点

临时顺序节点操作源码

@Slf4j
public class ZkSequenTemplateLock extends AbstractTemplateLock {private static final String zkServers = "127.0.0.1:2181";private static final int sessionTimeout = 8000;private static final int connectionTimeout = 5000;private static final String lockPath = "/lockPath";private String beforePath;private String currentPath;private ZkClient client;public ZkSequenTemplateLock() {client = new ZkClient(zkServers);if (!client.exists(lockPath)) {client.createPersistent(lockPath);}log.info("zk client 连接成功:{}",zkServers);}@Overrideprotected void waitLock() {CountDownLatch latch = new CountDownLatch(1);IZkDataListener listener = new IZkDataListener() {@Overridepublic void handleDataDeleted(String dataPath) throws Exception {System.out.println("监听到节点被删除");latch.countDown();}@Overridepublic void handleDataChange(String dataPath, Object data) throws Exception {}};//给排在前面的节点增加数据删除的watcher,本质是启动另一个线程去监听上一个节点client.subscribeDataChanges(beforePath, listener);//阻塞自己if (client.exists(beforePath)) {try {System.out.println("阻塞"+currentPath);latch.await();} catch (InterruptedException e) {e.printStackTrace();}}//取消watcher注册client.unsubscribeDataChanges(beforePath, listener);}@Overrideprotected boolean tryLock() {if (currentPath == null) {//创建一个临时顺序节点currentPath = client.createEphemeralSequential(lockPath + "/", "lock-data");System.out.println("current:" + currentPath);}//获得所有的子节点并排序。临时节点名称为自增长的字符串List<String> childrens = client.getChildren(lockPath);//排序list,按自然顺序排序Collections.sort(childrens);if (currentPath.equals(lockPath + "/" + childrens.get(0))) {return true;} else {//如果当前节点不是排第一,则获取前面一个节点信息,赋值给beforePathint curIndex = childrens.indexOf(currentPath.substring(lockPath.length() + 1));beforePath = lockPath + "/" + childrens.get(curIndex - 1);}System.out.println("beforePath"+beforePath);return false;}@Overridepublic void releaseLock() {System.out.println("delete:" + currentPath);client.delete(currentPath);}
}

Curator分布式锁工具

curator提供了以下种类的锁:

  • 共享可重入锁(Shared Reentrant Lock):全局同步锁,同一时间不会有两个客户端持有一个锁

  • 共享锁:与共享可重入锁类似,但是不可重入(有时候会因为这个原因造成死锁)

  • 共享可重入读写锁

  • 共享信号量

  • Multi Shared Lock:管理多种锁的容器实体

我们采用第一种Shared Reentrant Lock中的InterProcessMutex来完成上锁、释放锁的的操作

public class ZkLockWithCuratorTemplate implements Lock {// zk host地址private String host = "localhost";// zk自增存储nodeprivate String lockPath = "/curatorLock";// 重试休眠时间private static final int SLEEP_TIME_MS = 1000;// 最大重试1000次private static final int MAX_RETRIES = 1000;//会话超时时间private static final int SESSION_TIMEOUT = 30 * 1000;//连接超时时间private static final int CONNECTION_TIMEOUT = 3 * 1000;//curator核心操作类private CuratorFramework curatorFramework;InterProcessMutex lock;public ZkLockWithCuratorTemplate() {curatorFramework = CuratorFrameworkFactory.builder().connectString(host).connectionTimeoutMs(CONNECTION_TIMEOUT).sessionTimeoutMs(SESSION_TIMEOUT).retryPolicy(new ExponentialBackoffRetry(SLEEP_TIME_MS, MAX_RETRIES)).build();curatorFramework.start();lock = new InterProcessMutex (curatorFramework, lockPath);}@Overridepublic void getLock() throws Exception {//5s后超时释放锁lock.acquire(5, TimeUnit.SECONDS);}@Overridepublic void unlock() throws Exception {lock.release();}
}

源码以及测试类地址

https://github.com/Motianshi/distribute-tool

热门内容:再见,FastJson...技巧:MyBatis 中的trim标签,好用!
求求你们了,别再写满屏的 try catch 了!!“干掉” Date,Java8 LocalDate 真香!
浅析 VO、DTO、DO、PO 的概念、区别和用处!学会 IDEA REST Client后,postman就可以丢掉了...
Java 处理 Exception 的 9 个最佳实践!最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡

肝一下ZooKeeper实现分布式锁的方案,附带实例!相关推荐

  1. 浅析redis与zookeeper构建分布式锁的异同

    作者:架构小菜 链接:https://www.jianshu.com/p/508620a76e00 进程请求分布式锁时一般包含三个阶段:1. 进程请求获取锁:2. 获取到锁的进程持有锁并执行业务逻辑: ...

  2. 面试官:聊聊你对分布式锁技术方案的理解

    前言       由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题. 第一步,自身的业务场景: 在我日常做的项目中,目前 ...

  3. 【Zookeeper】基于Zookeeper实现分布式锁

    1.概述 转载:基于Zookeeper实现分布式锁 1.1 为什么使用分布式锁 我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,我们往往采用synchronized或者Lock ...

  4. Java基础学习总结(147)——Java常用分布式锁技术方案

    前言 由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题.所以自己结合实际工作中的一些经验和网上看到的一些资料,做一个讲解 ...

  5. 基于Zookeeper的分布式锁

    实现分布式锁目前有三种流行方案,分别为基于数据库.Redis.Zookeeper的方案,其中前两种方案网络上有很多资料可以参考,本文不做展开.我们来看下使用Zookeeper如何实现分布式锁. 什么是 ...

  6. 基于ZooKeeper的分布式锁和队列

    分布式锁的几种实现: 1.zookeeper分布式锁,基于自增节点 2.Redis分布式锁,基于setnx命令, 基于Redis实现分布式锁:http://blog.csdn.net/daiyudon ...

  7. 分布式锁-这一篇全了解(Redis实现分布式锁完美方案)

    前言 在某些场景中,多个进程必须以互斥的方式独占共享资源,这时用分布式锁是最直接有效的. 随着技术快速发展,数据规模增大,分布式系统越来越普及,一个应用往往会部署在多台机器上(多节点),在有些场景中, ...

  8. Redis分布式锁-这一篇全了解(Redission实现分布式锁完美方案)

    前言 在某些场景中,多个进程必须以互斥的方式独占共享资源,这时用分布式锁是最直接有效的. 随着技术快速发展,数据规模增大,分布式系统越来越普及,一个应用往往会部署在多台机器上(多节点),在有些场景中, ...

  9. 利用Zookeeper实现 - 分布式锁

    微信原文: 利用Zookeeper实现 - 分布式锁 博客原文:利用Zookeeper实现 - 分布式锁 在许多场景中,数据一致性是一个比较重要的话题,在单机环境中,我们可以通过Java提供的并发AP ...

最新文章

  1. 多视图几何三维重建实战系列之R-MVSNet
  2. NHibernate之Mapping 之 Property
  3. 鸿蒙os内测版应用名称,鸿蒙OS2.0发布,只有两款机型可以申请内测
  4. 求有环单链表的环连接点位置
  5. WP7 网络请求之WebClient
  6. C# 虹软离线SDK引擎 人脸识别
  7. 中国节能装备与产品市场“十四五”规划及2035年远景目标建议报2022-2028年
  8. win7_ fiddler 证书安装失败解决方法
  9. MFC——CWnd类
  10. cocos creator周边工具开发【spine预览】小记
  11. 使用PlayCanvas制作一个简单的小游戏(二)
  12. 电脑网络连接不上怎么办
  13. Linux--date命令
  14. 如何快速访问iOS设置:iPad,iPhone,iPod
  15. Linux - systemd 依赖
  16. k型热电偶材料_K型热电偶规格参数及使用性质.doc
  17. TLC5615的CVAVR程序
  18. 我国电子商务经济发展现状及其特点
  19. 2019物业管理师证怎么报名有哪些条件
  20. 错误代码0x800C0133错误问题的解决方案

热门文章

  1. 点击TableView任一行跳转详情页面会跳转两次的解决办法
  2. 两个asp.net发送邮件类
  3. 技术图文:字典技术在求解算法题中的应用
  4. win10+Chrome浏览器截长图方法
  5. 【ACM】与全排列相关的STL函数 prev_permutation next_permutation
  6. 【CTF】实验吧 凯撒变异
  7. 终于有人站出来为程序员说话了
  8. 程序员千万不要选全栈开发
  9. 雷军的最后一次 重 大 创 业
  10. 深耕智能制造和超高清视频领域,思谋科技获数千万美元融资