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. Jacl 是 TCL 的一个备用实现

    Jacl 是 TCL 的一个备用实现,它是完全使用 Java 代码编写的. wsadmin 工具使用 Jacl V1.3.2. 建议不要在 wsadmin 工具中使用 Jacl 语法 建议不要使用一个 ...

  2. (0.2.7)Mysql安装——多实例安装

    (0.2.6)Mysql安装——多实例安装 待完善

  3. VoIP应用系统大盘点

    一.VoIP拓扑 PBX是程控交换机,程控交换机有实体交换机和软件模拟的交换机. 软件模拟的交换机,即交换机服务器,常用开源的sip服务器有asterisk,freepbx, opensip等,商用的 ...

  4. 《FLASH PROGRAMMING 那些事》总结

    注明来自 http://www.ssdfans.com/?p=5589 以MLC为例: 对FGF(Floating Gate Flash)技术的,MLC programming一般分两步走:先prog ...

  5. POJ3176:Cow Bowling(数字三角形问题)

    地址:http://poj.org/problem?id=3176 题目解析:没什么好说的,之前上课时老师讲过.从下往上找,每一个三角形的顶点可由两个角加上顶点的值 两种方式得到 ,用dp数组保存下最 ...

  6. PAT 1063 Set Similarity[比较]

    1063 Set Similarity (25 分) Given two sets of integers, the similarity of the sets is defined to be N ...

  7. django 用户注册功能实现

    增加views的类 class RegisterView(View): def get(self, request): return render(request, 'register.html', ...

  8. iOS学习之flappyBird游戏的实现

    导言 在本人还是学生的时候,flappyBird这款游戏非常火爆,最后等到Android版的出来之后,也是很痴迷的玩了一把.可是,本人游戏天赋一直平平,几度玩得想摔手机.本文主要介绍如何开发iOS平台 ...

  9. Python(线程进程2)

    新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的: 1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像.同样的环境字符 ...

  10. Font: a C++ class

    Font: a C++ class        This class is used in Fractal Generator.    Avi Examples The header fileFon ...