原理
基本方案是基于ZooKeeper的临时节点与和watch机制。当要获取锁时在某个目录下创建一个临时节点,创建成功则表示获取锁成功,创建失败则表示获取锁失败,此时watch该临时节点,当该临时节点被删除后再去尝试获取锁。临时节点好处在于,当客户端崩溃后自动删除临时节点的同时锁也被释放了。该方案有个缺点缺点,就是大量客户端监听同一个节点,当锁释放后所有等待的客户端同时尝试获取锁,并发量很大。因此有了以下优化的方案。
优化方案基于ZooKeeper的临时有序节点和watch机制。当要获取锁时在某个目录下创建一个临时有序节点,每次创建均能成功,只是返回的节点序号不同。只有返回的节点序号是该目录下最小的才表示获取锁成功,否则表示获取锁失败,此时watch节点序号比本身小的前一个节点,当watch的节点被删除后再去尝试获取锁。
 
示例
1.使用Maven引入ZooKeeper客户端:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.6</version>
</dependency>
2.工具类
package com.example.demo;
 
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
 
public class ZkLockUtils implements Watcher {
 
private ZooKeeper zk;
 
/**
* 锁根节点
*/
private String lockRoot;
 
/**
* 锁子节点前缀
*/
private String lock;
 
/**
* 锁子节点分隔字符串
* 用于筛选锁根节点下和锁相关的子节点
*/
private String splitStr;
 
/**
* 要监听的锁子节点
*/
private String watchNode;
 
/**
* 当前创建的锁子节点
*/
private String currentNode;
 
/**
* 等待计数器
*/
private CountDownLatch latch;
 
/**
* 初始化
*
* @param connectString 连接字符串,如果多个则使用逗号分隔,例如127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002
* @param lockRoot 锁根节点,“/”开头
* @param lock 锁子节点前缀,不带“/”
* @param splitStr 锁子节点分隔字符串
* @throws Exception Exception
*/
public ZkLockUtils(String connectString, String lockRoot, String lock, String splitStr) throws Exception {
this.lockRoot = lockRoot;
this.lock = lock;
if (lock.contains(splitStr)) {
throw new RuntimeException("lock不能包含splitStr");
}
this.splitStr = splitStr;
 
// 会话超时时间30秒
int sessionTimeout = 30000;
// 初始化zk客户端,监视器设置为当前类
zk = new ZooKeeper(connectString, sessionTimeout, this);
// 如果锁根节点不存在,则创建
Stat stat = zk.exists(lockRoot, false);
if (stat == null) {
zk.create(lockRoot, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
 
/**
* 收到通知时执行
*/
public void process(WatchedEvent event) {
if (latch != null) {
latch.countDown();
}
}
 
/**
* 获取锁,如果超过等待时间,则获取锁失败
*
* @param waitTime 等待时间
* @param timeUnit 时间单位
* @return true(成功) false(失败)
* @throws Exception Exception
*/
public boolean lock(long waitTime, TimeUnit timeUnit) throws Exception {
// 创建临时有序锁子节点
currentNode = zk.create(lockRoot + "/" + lock + splitStr, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// 取出所有锁根节点的子节点
List<String> childNodes = zk.getChildren(lockRoot, false);
// 筛选锁根节点下和锁相关的子节点
List<String> lockChildNodes = new ArrayList<>();
for (String childNode : childNodes) {
String str = childNode.split(splitStr)[0];
if (str.equals(lock)) {
lockChildNodes.add(childNode);
}
}
// 升序排序
Collections.sort(lockChildNodes);
// 如果是最小的锁子节点,则获得锁
if (currentNode.equals(lockRoot + "/" + lockChildNodes.get(0))) {
System.out.println("当前是最小的锁子节点,获取锁成功");
return true;
}
// 如果不是最小的锁子节点,找到索引比自己小1的锁子节点
String previousNode = currentNode.substring(currentNode.lastIndexOf("/") + 1);
watchNode = lockChildNodes.get(Collections.binarySearch(lockChildNodes, previousNode) - 1);
 
// 判断要监听的锁子节点是否存在并监听该节点,如果不存在则该节点已被删除,直接获得锁
Stat stat = zk.exists(lockRoot + "/" + watchNode, true);
if (stat != null) {
latch = new CountDownLatch(1);
boolean b = latch.await(waitTime, timeUnit);
latch = null;
if (b) {
System.out.println("监听到锁子节点被删除,获取锁成功");
return true;
} else {
System.out.println("等待超时,获取锁失败");
return false;
}
} else {
System.out.println("要监听的锁子节点不存在,获取锁成功");
return true;
}
}
 
/**
* 释放锁
*/
public void unlock() throws Exception {
// version为-1表示匹配任何版本
zk.delete(currentNode, -1);
watchNode = null;
currentNode = null;
zk.close();
}
}
3.测试
package com.example.demo;
 
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
 
public class Demo {
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(2);
 
Thread thread1 = new Thread(() -> {
ZkLockUtils zkLockUtils = null;
try {
zkLockUtils = new ZkLockUtils("127.0.0.1:3181,127.0.0.1:3182,127.0.0.1:3183", "/locks", "lock", "_");
zkLockUtils.lock(5, TimeUnit.SECONDS);
Thread.sleep(10000);
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (zkLockUtils != null) {
try {
zkLockUtils.unlock();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
 
thread1.start();
 
Thread thread2 = new Thread(() -> {
ZkLockUtils zkLockUtils = null;
try {
zkLockUtils = new ZkLockUtils("127.0.0.1:3181,127.0.0.1:3182,127.0.0.1:3183", "/locks", "lock", "_");
zkLockUtils.lock(5, TimeUnit.SECONDS);
Thread.sleep(10000);
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (zkLockUtils != null) {
try {
zkLockUtils.unlock();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
 
thread2.start();
 
countDownLatch.await();
System.out.println("所有子线程都执行完毕");
}
}
 
优缺点
1.Redis分布式锁需要轮询获取锁,性能开销较大。ZooKeeper分布式锁基于watch机制监听节点,不需要轮询获取锁,性能开销较小。
2.如果Redis获取锁的客户端非正常退出,那么只能等待超时时间之后才能释放锁。Redis因为创建的是临时节点,只要客户端崩溃或者连接断开,临时节点就会被删除,锁也就被立即释放了。

ZooKeeper-3.5.6分布式锁的更多相关文章

  1. Zookeeper是如何实现分布式锁的

    [toc] Zookeeper是如何实现分布式锁的 标签 : Zookeeper 分布式 实现分布式锁要考虑的重要问题 1. 三个核心要素 加锁, 解锁, 锁超时 2. 三个问题 要保证原子性操作, ...

  2. zookeeper适用场景:分布式锁实现

    问题导读:1.zookeeper如何实现分布式锁?2.什么是羊群效应?3.zookeeper如何释放锁? 在zookeeper应用场景有关于分布式集群配置文件同步问题的描述,设想一下如果有100台机器 ...

  3. 服务注册中心之ZooKeeper系列(三) 实现分布式锁

    通过ZooKeeper的有序节点.节点路径不回重复.还有节点删除会触发Wathcer事件的这些特性,我们可以实现分布式锁. 一.思路 zookeeper中创建一个根节点Locks,用于后续各个客户端的 ...

  4. zookeeper【5】分布式锁

    我们常说的锁是单进程多线程锁,在多线程并发编程中,用于线程之间的数据同步,保护共享资源的访问.而分布式锁,指在分布式环境下,保护跨进程.跨主机.跨网络的共享资源,实现互斥访问,保证一致性. 架构图: ...

  5. 基于zookeeper或redis实现分布式锁

    前言 在分布式系统中,分布式锁是为了解决多实例之间的同步问题.例如master选举,能够获取分布式锁的就是master,获取失败的就是slave.又或者能够获取锁的实例能够完成特定的操作. 目前比较常 ...

  6. 基于ZooKeeper的三种分布式锁实现

    [欢迎关注公众号:程序猿讲故事 (codestory),及时接收最新文章] 今天介绍基于ZooKeeper的分布式锁的简单实现,包括阻塞锁和非阻塞锁.同时增加了网上很少介绍的基于节点的非阻塞锁实现,主 ...

  7. Zookeeper绍二(分布式锁介)

    一.为什么会有分布式锁? 在多线程环境下,由于上下文的切换,数据可能出现不一致的情况或者数据被污染,我们需要保证数据安全,所以想到了加锁. 所谓的加锁机制呢,就是当一个线程访问该类的某个数据时,进行保 ...

  8. 跟着实例学习ZooKeeper的用法: 分布式锁

    锁 分布式的锁全局同步, 这意味着任何一个时间点不会有两个客户端都拥有相同的锁. 可重入锁Shared Reentrant Lock 首先我们先看一个全局可重入的锁. Shared意味着锁是全局可见的 ...

  9. Zookeeper系列3 实现分布式锁

    基本思路 1 client调用create()方法创建“/locks/_lock_”临时顺序节点,注意节点类型是EPHEMERAL_SEQUENTIAL 2 client调用getChildren(& ...

  10. ZooKeeper 分布式锁实现

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

随机推荐

  1. 单元测试与单元测试框架 Jest

    什么是单元测试? 测试是一种验证我们的代码是否可以按预期工作的手段. 被测试的对象可以是我们程序的任何一个组成部分.大到一个分为多步骤的下单流程,小到代码中的一个函数. 单元测试特指被测试对象为程序中 ...

  2. vue+elementUI+vue-i18n 实现国际化

    在main.js同级建i18n文件夹,并里面建i18n.js.langs文件夹,langs文件夹下建en.js.cn.js目录如下: . ├── App.vue ├── assets │   └── ...

  3. VuePress初探(一)

    原文参考链接 手把手教你使用 VuePress 搭建个人博客 有阅读障碍的同学,可以跳过第一至四节,下载我写好的工具包: git clone https://github.com/zhangyunch ...

  4. org.xml.sax.SAXParseException: The processing instruction target matching "[xX][mM][lL]" is not allowed(转)

    xml文件不能被正确解析/The processing instruction target matching "[xX][mM][lL]" is not allowed. The ...

  5. 【HttpRunner v3.x】笔记 ——4. 测试用例-结构解析

    一.官方首推pytest格式 上篇文章我们知道了,httprunner可以支持三种格式的用例,分别是pytest.yaml和json.yaml和json是以前的版本所使用的用例格式,但是在3.x版本上 ...

  6. Zabbix 5.0 LTS版本的安装小结

    Zabbix 5.0 LTS版本的安装小结   1:准备Zabbix的服务器. 这里可能需要一台或多台服务器,视需求和资源而定.也可以将Zabbix_Server.MySQL.Zabbix Web等安 ...

  7. mybatis-spring-boot-starter 1.3.0 操作实体类的SpringBoot例子

    例程下载:https://files.cnblogs.com/files/xiandedanteng/gatling20200428-02.zip 需求:使用mybatis实现对hy_emp表的CRU ...

  8. 2.AVFormatContext和AVInputFormat

    参考https://blog.csdn.net/leixiaohua1020/article/details/14214705 AVFormatContext: 用来存储视音频封装格式(flv,mp4 ...

  9. Oracle Rman备份恢复和管理

    参考资料: Oracle之Rman入门指南 一步一步学Rman Rman简介 Rman-Recover manager恢复管理工具. Oracle集成了很多环境的一个数据库备份和恢复的工具. Rman ...

  10. python中一次性input3个整数,并用空格隔开怎么表示

    a,b,c=map(int,input('请输入3个整数用空格隔开:').split(' ')) map的使用方法:map(函数名,循环体)