ZooKeeper分布式锁简单实践

  在分布式解决方案中,Zookeeper是一个分布式协调工具。当多个JVM客户端,同时在ZooKeeper上创建相同的一个临时节点,因为临时节点路径是保证唯一,只要谁能够创建节点成功,谁就能够获取到锁。没有创建成功节点,就会进行等待,当释放锁的时候,采用事件通知给客户端重新获取锁资源。如果请求超时直接返回给客户端超时,重新请求即可。

  代码实现

  为了更好的展现效果,我这里设置每个线程请求需要1s,请求超时时间为30s。

  首先我们先写一个测试类,模拟多线程多客户端请求的情况:

  public class ZkLockTest implements Runnable {

  private ZkLock zkLock = new ZkDistributedLock();

  public void run() {

  try {

  if (zkLock.getLock((long)30000,null)) {

  System.out.println(线程: + Thread.currentThread().getName() + ,抢购成功: + System.currentTimeMillis());

  } else {

  System.out.println(线程: + Thread.currentThread().getName() + ,抢购超时失败请重试: + System.currentTimeMillis());

  }

  Thread.sleep(1000);

  } catch (Exception e) {

  } finally {

  zkLock.unLock();

  }

  }

  public static void main(String[] args) {

  System.out.println(zk分布式锁开始。。);

  for (int i = 0; i 100; i++) {

  new Thread(new ZkLockTest()).start();

  }

  }

  }

  模拟100个线程,去同时争夺锁。当然上述写法 100个线程不会同时启动,如果需要的话可以用信号量的形式控制。

  其次,写一个锁的接口

  public interface ZkLock {

  // 获取锁

  Boolean getLock(Long acquireTimeout,Long endTime);

  // 释放锁

  void unLock();

  }

  这里我定义了两个接口,分别对应获取锁和释放锁。

  在获取锁中有两个参数,含义分别为锁超时时间和最终计算的超时时间,具体看下文代码就懂了。

  public class ZkDistributedLock implements ZkLock {

  // 集群连接地址

  private String CONNECTION = 127.0.0.1:2181;

  // zk客户端连接

  private ZkClient zkClient = new ZkClient(CONNECTION);

  // path路径

  private String lockPath = /lock;

  private CountDownLatch countDownLatch;

  //请求设置的超时时间:acquireTimeout 毫秒。最终超时时间endTime

  public Boolean getLock(Long acquireTimeout,Long endTime) {

  Boolean lock = false;

  if (endTime == null) {

  //等待超时时间

  endTime = System.currentTimeMillis() + acquireTimeout;

  }

  if (tryLock()) {

  System.out.println(####获取锁成功######);

  lock = true;

  } else {

  if (waitLock(endTime)) {

  if (getLock(null,endTime)) {

  lock = true;

  }

  }

  }

  return lock;

  }

  public void unLock() {

  if (zkClient != null) {

  System.out.println(#######释放锁#########);

  zkClient.close();

  }

  }

  private boolean tryLock() {

  try {

  zkClient.createEphemeral(lockPath);

  return true;

  } catch (Exception e) {

  return false;

  }

  }

  private Boolean waitLock(Long endTime) {

  // System.out.println(进入等待);

  // 使用zk临时事件监听

  IZkDataListener iZkDataListener = null;

  try {

  // 使用zk临时事件监听

  iZkDataListener = new IZkDataListener() {

  public void handleDataDeleted(String path) throws Exception {

  if (countDownLatch != null) {

  countDownLatch.countDown();

  }

  }

  public void handleDataChange(String arg0, Object arg1) throws Exception {

  }

  };

  // 注册事件通知

  zkClient.subscribeDataChanges(lockPath, iZkDataListener);

  if (System.currentTimeMillis() endTime) {

  if (zkClient.exists(lockPath)) {

  countDownLatch = new CountDownLatch(1);

  try {

  countDownLatch.await();

  return true;

  } catch (Exception e) {

  }

  } else {

  return true;

  }

  } else {

  System.out.println(超时返回);

  }

  } catch (Exception e) {

  } finally {

  // 监听完毕后,移除事件通知

  zkClient.unsubscribeDataChanges(lockPath, iZkDataListener);

  }

  return false;

  }

  }

  这个类是我实现zk锁的核心类,和上文原理图中类似。首先用户请求的时候需要获取锁,第一个争夺到锁的用户执行相关逻辑后释放锁,在这个过程中如果程序出错断开连接,因为临时节点的缘故,节点也会自动删除释放锁的。

  另外就是其他争夺锁失败的用户,我这里设置了一定的等待时间,当在时间内原锁释放,还是可以重新去获取锁的。这里要说下锁释放的监听,在原生的zookeeper中,使用watcher需要每次先注册,而且使用一次就需要注册一次。而在zkClient中,没有注册watcher的必要,而是引入了listener的概念,即只要client在某一个节点中注册了listener,只要服务端发生变化,就会通知当前注册listener的客户端。我这里使用的是IZkDataListener,这个类是zkClient提供的一个接口,它可以在当前节点数据内容或版本发生变化或者当前节点被删除时触发。

  触发后我们就可以重新去争夺锁,当再次争夺失败进入等待时会再次检测当前请求是否超时。

  下面我们来看下上述代码的实现效果:

  zk分布式锁开始。。

  ####获取锁成功######

  线程:Thread-3,抢购成功:1544183770509

  #######释放锁#########

  ####获取锁成功######

  线程:Thread-81,抢购成功:1544183771555

  #######释放锁#########

  .........

  超时返回

  线程:Thread-11,抢购超时失败请重试:1544183800677

  超时返回

  线程:Thread-1,抢购超时失败请重试:1544183800681

  #######释放锁#########

  #######释放锁#########

  ####获取锁成功######

  线程:Thread-49,抢购成功:1544183801710

  超时返回

  线程:Thread-25,抢购超时失败请重试:1544183801729

  超时返回

  #######释放锁#########

  #######释放锁#########

  释放锁说的可能并不准确,应该说是关闭连接,有些线程实际上是没有得到锁的。

  简单尝试了下zk实现分布式锁的方式,当然上述代码如果应用到生产中肯定问题还是不少的,因为兴趣点不在这,就不仔细研究了。简单来说,相比其他方式实现步骤更为复杂,感觉更容易出问题。

  总结

  经过三种方式的应用和简单实践,总结实现分布式锁三种方式的优缺点如下

  1、数据库实现:

  优点,实现简单只是for update的显示加锁。缺点,性能问题较大,而且本身系统在设计时是需要尽量减轻数据库的压力的。

  2、Redis实现:

  优点:一般互联网项目都会集成,本身是nosql数据库,缓存实现简单,高并发应付自如,同时新版的Jedis完美解决了以往程序出错,未设置超时时间死锁的问题。

  缺点:网络问题可能会引起锁删除失败,超时时间有一定的延迟。

  3、ZooKeeper实现:

  优点:Zookeeper临时节点先天可控的有效期设置,避免了程序引发的死锁问题

  缺点:实现过于繁杂,相比其他两种写法更容易出问题,另外还需要单独维护zk。

  结论:

  我个人更为推荐Redis的实现方式,实现简单,性能也比较好,同时引入集群可以提高可用性。Jedis多参的设置方式也较好的保证了有效期的控制和死锁的问题

ZooKeeper分布式锁简单实践的更多相关文章

  1. zookeeper分布式锁简单实现(JavaApi)

    1.创建会话连接 package com.karat.cn.zookeeperAchieveLock.javaapilock; import org.apache.zookeeper.WatchedE ...

  2. Zookeeper 分布式锁 (图解+秒懂+史上最全)

    文章很长,而且持续更新,建议收藏起来,慢慢读! 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : 极致经典 + 社群大片好评 < Java 高并发 三 ...

  3. ZooKeeper 分布式锁

    在Redis分布式锁一文中, 作者介绍了如何使用Redis开发分布式锁. Redis分布式锁具有轻量高吞吐量的特点,但是一致性保证较弱.我们可以使用Zookeeper开发分布式锁,来满足对高一致性的要 ...

  4. [转载] zookeeper 分布式锁服务

    转载自http://www.cnblogs.com/shanyou/archive/2012/09/22/2697818.html 分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那 ...

  5. 跟着大神学zookeeper分布式锁实现-----来自Ruthless

    前几天分享了@Ruthless大神的Redis锁,发现和大家都学习了很多东西.因为分布式锁里面,最好的实现是zookeeper的分布式锁.所以在这里把实现方式和大家分享一下. zookeeper分布式 ...

  6. 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  7. zookeeper 分布式锁原理

    zookeeper 分布式锁原理: 1 大家也许都很熟悉了多个线程或者多个进程间的共享锁的实现方式了,但是在分布式场景中我们会面临多个Server之间的锁的问题,实现的复杂度比较高.利用基于googl ...

  8. 分布式锁(一) Zookeeper分布式锁

    什么是Zookeeper? Zookeeper(业界简称zk)是一种提供配置管理.分布式协同以及命名的中心化服务,这些提供的功能都是分布式系统中非常底层且必不可少的基本功能,但是如果自己实现这些功能而 ...

  9. ZooKeeper分布式锁的实现原理

    七张图彻底讲清楚ZooKeeper分布式锁的实现原理[石杉的架构笔记] 文章转载自:https://juejin.im/post/5c01532ef265da61362232ed#comment(写的 ...

随机推荐

  1. Python 最难的问题

    Python 最难的问题 超过十年以上,没有比解释器全局锁(GIL)让Python新手和专家更有挫折感或者更有好奇心. 未解决的问题 随处都是问题.难度大.耗时多肯定是其中一个问题.仅仅是尝试解决这个 ...

  2. SQL Server 排名函数

    个函数进行的解释. 以下是对这4个函数的解释: RANK() 返回结果集的分区内每行的排名.行的排名是相关行之前的排名数加一. 假设两个或多个行与一个排名关联,则每一个关联行将得到同样的排名. 比如, ...

  3. 前端 html body 内标签之input

    可以做登录页面 text是文本输入框 <!DOCTYPE html> <html lang="en"> <head> <meta char ...

  4. python web框架 django wsgi 理论

    django wsgi python有个自带的wsgi模块 可以写自定义web框架 用wsgi在内部创建socket对象就可以了 自己只写处理函数就可以了django只是web框架 他也不负责写soc ...

  5. HDU2425:Hiking Trip(简单bfs,优先队列实现)

    题目: 传送门 题意很简单就不解释了,水题一道. #include <iostream> #include <string.h> #include <stdio.h> ...

  6. 工作笔记——js前端规范

    去年年末做了一个项目,因为第一次做前端管理职位,第一次做整个项目的前端架构很多东西都不熟悉,作为一次大胆的尝试. js方面的只有一个坑,那就是前端与后端的网络层封装,这一块是在后端的协助下开发的.网络 ...

  7. HDU 1532 Drainage Ditches(网络流模板题)

    题目大意:就是由于下大雨的时候约翰的农场就会被雨水给淹没,无奈下约翰不得不修建水沟,而且是网络水沟,并且聪明的约翰还控制了水的流速, 本题就是让你求出最大流速,无疑要运用到求最大流了.题中m为水沟数, ...

  8. Ubuntu离线安装Tensorflow keras

    实验室的服务器木有连接外网,无法使用pip进行安装,真正意义上的离线安装需要很多安装依赖包,下载起来很麻烦,而且版本号还不一定对. 另一种方法是利用源代码编译安装,那样也需要安装依赖包,比较麻烦. 经 ...

  9. iClap助力移动互联网企业高效实现规范化管理

    移动互联网的迅速崛起,智能移动客户端深刻而全面地影响着人类生活与工作习惯.而企业办公已从原始的纸张办公,到固定PC办公,跨入到一个应用范围更广.效率更高的移动办公时代.由静生动,让企业办公更加人性化和 ...

  10. 2017 Multi-University Training Contest - Team 1 03Colorful Tree

    地址:http://acm.split.hdu.edu.cn/showproblem.php?pid=6035 题面: Colorful Tree Time Limit: 6000/3000 MS ( ...