一.前言

在之前的文章中介绍过分布式锁的特点和利用Redis实现简单的分布式锁。但是分布式锁的实现还有很多其他方式,但是万变不离其宗,始终遵循一个特点:同一时刻只能有一个操作获取。这篇文章主要介绍如何基于zookeeper实现分布式锁。

  • zookeeper能够作为分布式锁实现的基础
  • 算法流程
  • 实现

关于分布式锁的相关特性,这里不再赘述,请参考分布式锁

### 二.zookeeper能够作为分布式锁实现的基础

这里回顾下分布式锁的特点:

  • 每次只能一个占用锁;
  • 可以重复进入锁;
  • 只有占用者才可以解锁;
  • 获取锁和释放锁都需要原子
  • 不能产生死锁
  • 尽量满足性能

zookeeper中有一种临时顺序节点,它具有以下特征:

  • 时效性,当会话结束,节点将自动被删除
  • 顺序性,当多个应用向其注册顺序节点时,每个顺序号将只能被一个应用获取

利用以上的特点可以满足分布式锁实现的基本要求:

  1. 因为顺序性,可以让最小顺序号的应用获取到锁,从而满足分布式锁的每次只能一个占用锁,因为只有它一个获取到,所以可以实现重复进入,只要设置标识即可。锁的释放,即删除应用在zookeeper上注册的节点,因为每个节点只被自己注册拥有,所以只有自己才能删除,这样就满足只有占用者才可以解锁

  2. zookeeper的序号分配是原子的,分配后即不会再改变,让最小序号者获取锁,所以获取锁是原子的

  3. 因为注册的是临时节点,在会话期间内有效,所以不会产生死锁

  4. zookeeper注册节点的性能能满足几千,而且支持集群,能够满足大部分情况下的性能

三.算法流程

1.获取锁

需要获取分布式锁的应用都向zookeeper的/lock/{resouce}目录下注册sequence-前缀的节点,序号最小者获取到操作资源的权限:

Note:

这里的resource需要依据竞争的具体资源确定,如竞争账户则可以使用账户号作为resource。

从图中可以看出,clientA的顺序号最小,由它获取到锁,操作资源。

算法步骤

  1. client判断/lock目录是否存在,如果不存在则向其注册/lock的持久节点
  2. client判断/lock目录下是否存在竞争的资源resouce目录,如果不存在则向其注册/lock/resource的持久节点
  3. client向/lock/resource目录下注册/lock/resource/sequence-前缀的临时顺序节点,并得到顺序号
  4. client获取/lock/resource目录下的所有临时顺序子节点
  5. client判断临时子节点序号中是否存在比自身的序号小的节点。如果不存在,则获取到锁;如果存在,则对象该临时节点做watch监控
  6. 如果收到监控的临时节点被删除的通知,则再重复4、5步骤,直到获取到锁

流程图

2.释放锁

因为最小的节点只被获取到锁的client持有,所以该锁不可能被其他client释放。同时释放锁只需要将临时顺序节点删除,也是原子性操作。

### 三.实现

/**
* 基于Zookeeper实现分布式锁
*
* @author huaijin
*/
public class DistributedLockBaseZookeeper implements DistributedLock { private static final Logger log = LoggerFactory.getLogger(DistributedLockBaseZookeeper.class); /**
* 利用空串作为各个节点存储的数据
*/
private static final String EMPTY_DATA = ""; /**
* 分布式锁的根目录
*/
private static final String LOCK_ROOT = "/lock"; /**
* zookeeper目录分隔符
*/
private static final String PATH_SEPARATOR = "/"; /**
* 临时顺序节点前缀
*/
private static final String LOCK_NODE_PREFIX = "sequence-"; /**
* 利用Lock和Condition实现等待通知
*/
private Lock waitNotifierLock = new ReentrantLock();
private Condition waitNotifier = waitNotifierLock.newCondition(); /**
* 操作zookeeper的client
*/
private ZkClient zkClient; /**
* 分布式资源的路径
*/
private String resourcePath; /**
* 锁节点完整前缀
*/
private String lockNodePrefix; /**
* 当前注册的临时顺序节点路径
*/
private String currentLockNodePath; public DistributedLockBaseZookeeper(String resource, ZkClient zkClient) {
Objects.requireNonNull(zkClient, "zkClient must not be null!");
if (resource == null || resource.isEmpty()) {
throw new IllegalArgumentException("resource must not be null!");
}
this.zkClient = zkClient;
this.resourcePath = LOCK_ROOT + PATH_SEPARATOR + resource;
this.lockNodePrefix = resourcePath + PATH_SEPARATOR + LOCK_NODE_PREFIX; // 创建分布式锁根目录
if (!this.zkClient.exists(LOCK_ROOT)) {
try {
this.zkClient.create(LOCK_ROOT, EMPTY_DATA, CreateMode.PERSISTENT);
} catch (ZkNodeExistsException e) {
// ignore, logging
log.warn("The root path for lock already exists.");
}
} // 创建资源目录
if (!this.zkClient.exists(resourcePath)) {
try {
this.zkClient.create(resourcePath, EMPTY_DATA, CreateMode.PERSISTENT);
} catch (ZkNodeExistsException e) {
// ignore, logging
log.warn("The resource path for [" + resourcePath + "] already exists.");
}
}
} @Override
public void lock() throws DistributedLockException {
if (!acquireLock()) {
// 如果获取锁不成功,则等待
waitNotifierLock.lock();
try {
waitNotifier.await();
} catch (Exception e) {
throw new DistributedLockException("Interrupt when waiting notification.");
} finally {
waitNotifierLock.unlock();
}
}
} @Override
public void unlock() {
// 删除自身节点,释放锁
zkClient.delete(currentLockNodePath);
} private boolean acquireLock() throws DistributedLockException {
// 如果当前未注册临时顺序节点,则注册
if (this.currentLockNodePath == null) {
this.currentLockNodePath = zkClient.create(lockNodePrefix, EMPTY_DATA, CreateMode.EPHEMERAL_SEQUENTIAL);
}
// 获取顺序号
long lockNodeSeq = fetchSeqFromNodePath(currentLockNodePath);
// 获取所有子节点
List<String> childNodePaths = zkClient.getChildren(resourcePath);
if (childNodePaths == null || childNodePaths.isEmpty()) {
throw new DistributedLockException("Not exists child nodes.");
}
// 从所有子节点中获取最小子节点的顺序号
long minSeq = 1000000L;
int minIndex = -1;
for (int i = 0; i < childNodePaths.size(); i++) {
long nodeSeq = fetchSeqFromNodePath(resourcePath + childNodePaths.get(i));
if (nodeSeq < minSeq) {
minSeq = nodeSeq;
minIndex = i;
}
}
// 比较自身顺序号与最小序号
if (lockNodeSeq > minSeq) {
// 如果存在更小序号,则监控最小序号的子节点
String minLockNodePath = childNodePaths.get(minIndex);
zkClient.subscribeDataChanges(resourcePath + PATH_SEPARATOR + minLockNodePath,
new ListenerForLockRelease());
return false;
}
// 成功获取锁,返回
return true;
} private long fetchSeqFromNodePath(String nodePath) {
String seq = nodePath.substring(lockNodePrefix.length());
return Long.valueOf(seq);
} private class ListenerForLockRelease implements IZkDataListener { @Override
public void handleDataChange(String dataPath, Object data) throws Exception {
} @Override
public void handleDataDeleted(String dataPath) throws Exception {
// 如果成功获取锁,则通知,让主线程返回
if (acquireLock()) {
waitNotifierLock.lock();
try {
waitNotifier.signal();
} finally {
waitNotifierLock.unlock();
}
}
}
}
}

zookeeper — 实现分布式锁的更多相关文章

  1. zookeeper实现分布式锁服务

    A distributed lock base on zookeeper. zookeeper是hadoop下面的一个子项目, 用来协调跟hadoop相关的一些分布式的框架, 如hadoop, hiv ...

  2. [ZooKeeper.net] 3 ZooKeeper的分布式锁

    基于ZooKeeper的分布式锁 ZooKeeper 里实现分布式锁的基本逻辑: 1.zookeeper中创建一个根节点(Locks),用于后续各个客户端的锁操作. 2.想要获取锁的client都在L ...

  3. 基于 Zookeeper 的分布式锁实现

    1. 背景 最近在学习 Zookeeper,在刚开始接触 Zookeeper 的时候,完全不知道 Zookeeper 有什么用.且很多资料都是将 Zookeeper 描述成一个“类 Unix/Linu ...

  4. zookeeper的分布式锁

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

  5. zookeeper 实现分布式锁安全用法

    zookeeper 实现分布式锁安全用法 标签: zookeeper sessionExpire connectionLoss 分布式锁 背景 ConnectionLoss 链接丢失 SessionE ...

  6. 基于Zookeeper的分布式锁

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

  7. 转载 [ZooKeeper.net] 3 ZooKeeper的分布式锁

    [ZooKeeper.net] 3 ZooKeeper的分布式锁   基于ZooKeeper的分布式锁  源码分享:http://pan.baidu.com/s/1miQCDKk ZooKeeper ...

  8. Redis与Zookeeper实现分布式锁的区别

    Redis实现分布式锁 1.根据lockKey区进行setnx(set not exist,如果key值为空,则正常设置,返回1,否则不会进行设置并返回0)操作,如果设置成功,表示已经获得锁,否则并没 ...

  9. Zookeeper系列四:Zookeeper实现分布式锁、Zookeeper实现配置中心

    一.Zookeeper实现分布式锁 分布式锁主要用于在分布式环境中保证数据的一致性. 包括跨进程.跨机器.跨网络导致共享资源不一致的问题. 1. 分布式锁的实现思路 说明: 这种实现会有一个缺点,即当 ...

  10. 10分钟看懂!基于Zookeeper的分布式锁

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

随机推荐

  1. tensor的加减乘和矩阵乘法

    对应的代码如下 import torch """如下是tensor乘法与加减法,对应位相乘或相加减,可以一对多""" def add_and ...

  2. STM32基本GPIO操作:点灯(库函数+寄存器)

    社团作业=_= 开发版上的LED灯负极连接在PB5口,正极串联一510Ω电阻后与3.3V相连 若开发板不带LED灯则需要自行连接,务必串联一个合适的电阻防止LED灯烧坏 零.一个有趣的延时函数 来自于 ...

  3. 一篇和Redis有关的锁和事务的文章

    部分参考链接 Transaction StackExchange.Redis Transaction hashest 正文 Redis 是一种基于内存的单线程数据库.意味着所有的命令是一个接一个的执行 ...

  4. 【RTOS】基于V7开发板的最新版uCOS-II V2.92.16程序模板,含MDK和IAR,支持uC/Probe

    模板下载: 链接:https://pan.baidu.com/s/10a9Hi0MD14obR_B1LAQEFA     提取码:z76n 1.MDK使用MDK5.26及其以上版本. 2.IAR使用I ...

  5. eclipse的一些常用快捷键

    掌握了eclipse快捷键功能,能够大大提高开发效率. 这里总结一些eclipse的常用快捷键. 编辑相关快捷键  1. [ALT+/]:此快捷键为用户编辑的好帮手,能为用户提供内容的辅助,不要为记不 ...

  6. Java题库——chapter7 多维数组

    1)Which of the following statements are correct? 1) _______ A)char[ ][ ] charArray = {{'a', 'b'}, {' ...

  7. 【NOI 2011】阿狸的打字机

    Problem Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有 \(28\) 个按键,分别印有 \(26\) 个小写英文字母和 B . P 两个字母. ...

  8. 网络协议 3 - 物理层 和 MAC 层

    在上一篇博文中,我们见证了 IP 地址的诞生,机器一旦有了 IP,就可以在网络的环境里和其他的机器展开沟通了.     今天,我们来认识下 物理层 和 MAC 层.     日常生活中,身为 90 后 ...

  9. JQuery 获取元素到浏览器可视窗口边缘的距离

    获取元素到浏览器可视窗口边缘的距离 by:授客 QQ:1033553122 1.   测试环境 JQuery-3.2.1.min.js 下载地址: https://gitee.com/ishouke/ ...

  10. 移动端H5页面遇到的问题总结(转载请注明出处)

    最近刚做完一个移动端的项目,产品之无敌,过程之艰辛,我就不多说了,记录下在这个项目中遇到的问题,以防万一,虽然这些可能都是已经被N多前辈解决掉了的问题,也放在这里,算是为自己漫漫前端路铺了一颗小石子儿 ...