原理
基本方案是基于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. mysql8.0的下载、安装、可视化软件(下载、安装、破解)

    获取下面相关资源,请关注微信公众号“带你做毕设或者添加小编微信,有不明白的联系小编,可以提供远程帮助哦 mysql压缩版最新版本下载地址https://dev.mysql.com/downloads/ ...

  2. py_选择排序

    # 选择排序 # 一趟排序记录最小值,放到第一个位置 #再一趟排序记录记录列表无序区最小的数,放到第二个位置 #.... # 关键点:有序区.无序区.无序区最小值 #方法一 def select_So ...

  3. 前端测试框架 Jest

    前端测试工具一览 前端测试工具也和前端的框架一样纷繁复杂,其中常见的测试工具,大致可分为测试框架.断言库.测试覆盖率工具等几类.在正式开始本文之前,我们先来大致了解下它们: 测试框架 测试框架的作用是 ...

  4. java开发,入职半年,对未来迷茫,如何发展?

    分享-更多精彩图片尽在大师助手 个人建议,在JAVA方面,先学好JAVA SE.不管如何,基础才是上层建筑的一切.推荐去看jdk源码,推荐1.6或者1.7版本.因为1.8版本的源码中会有很多lambd ...

  5. linux 下切换Python版本(某用户,共存,替换)

    当你安装 Debian Linux 时,安装过程有可能同时为你提供多个可用的 Python 版本,因此系统中会存在多个 Python 的可执行二进制文件.你可以按照以下方法使用 ls 命令来查看你的系 ...

  6. Java拷贝——深拷贝与浅拷贝

    深拷贝和浅拷贝 值类型 vs 引用类型 在Java中,像数组.类Class.枚举Enum.Integer包装类等等,就是典型的引用类型,所以操作时一般来说采用的也是引用传递的方式: 但是Java的语言 ...

  7. python小白入门基础(三:整型)

    # Number(int float str complex) #int 整型(正整数 0 负整数)intvar_1 = 100print(intvar_1)invar_2 = 0 print(inv ...

  8. Easy Game(记忆化搜索)

    You are playing a two player game. Initially there are n integer numbers in an array and player A an ...

  9. 【好文分享】为什么强烈禁止开发人员使用isSuccess作为变量名

    原文来自阿里云hollies:https://developer.aliyun.com/article/701413   简介: 在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提 ...

  10. 【API进阶之路】用API打造一条自动化内容生产流水线

    摘要:搞定了内容审核之后,我又把抓取工具.内容审核API.文本摘要生成API串联在一起,从抓到审再到编,建立了一条自动化的内容生产流水线,编辑团队只需要做优质内容的推荐就可以了. 上周,运营部将官网上 ...