轻轻松松半小时搞懂两种分布式锁(redis,zookeeper)实现及原理

分布式锁讲解:

在讲分布式锁之前,我们先了解一下什么是分布式锁,为什么要使用分布式锁。

  1. 什么是分布式锁?
    分布式锁是在分布式系统中所使用的一种锁机制,是作用于多个JVM之间的锁机制。
  2. 为什么要使用分布式锁?
    在这里插入图片描述

为了防止在分布式系统中,多个JVM操作同一块资源而造成资源协调问题,例如上图。
3. 分布式锁实现原理
其实所有的锁实现原理都是一样的,分三步走:
第一步:获取锁
第二步:等待获取
第三步:释放锁
只是不同的中间件基于各自的特点,写法不一样,但是实现思路基本是一致的,比如基于redis,由于redis的key不能重复,因此key可以作为锁,且只有一把,而基于zk的原理则是,zk的节点路径不能重复,是唯一的,因此锁也是唯一的。

  1. 基于redis实现分布式锁
    按照上面三部,我们来写写实现思路:

第一步:获取锁,多个JVM去redis上创建一个key,这个key就是锁,由于redis是单线程的,因此只有一个JVM能创建成功,谁创建成功了,谁就获取了锁,没有创建成功的则继续等待,而且key是不能重复的,因此锁是唯一的,我们使用redis的setIfAsbent命令,创建成功返回true,否则返回false

第二步:等待获取,我们可以设置等待时间,在时间内则继续等待,否则获取锁失败,也可以一直等待,这里是超时就失败

第三步:释放锁,业务逻辑处理完成后,将锁释放,此时其他的JVM继续获取锁

上代码:
在这里插入图片描述

**

## 获取锁的源码:

**
引包:
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

  /**
   * 获取锁
   *
   * @param lockKey 锁的key值
   * @param waitTime 已经有一个jvm获取了锁,其他jvm的等待时间
   * @param timeOut 超出设置的等待时间,比如释放锁的方法报错等异常导致没有释放掉锁,为了防止死锁,设置超时时间,超出时间,则自动释放
   */
  public String lock(String lockKey, long waitTime, long timeOut) {
    // 获取锁的最终时间
    long endTime = System.currentTimeMillis() + waitTime;
    // 当前时间小于最终截止时间,则其他的jvm继续等待
    while (System.currentTimeMillis() < endTime) {
      String lockValue = UUID.randomUUID().toString();
      // 为true则说明锁之前还未创建,现在新创建
      if (redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue)) {
        redisTemplate.expire(lockKey, timeOut, TimeUnit.MILLISECONDS);
        // 退出循环
        return lockValue;
      }
      // 其他jvm线程继续等待超时
    }
    return null;
  }

在这里插入图片描述

**

## 释放锁的源码:

**
  /**
   * 释放锁
   *
   * @param lockKey 锁的key值
   * @param lockValue value值
   * @return
   */
  public boolean unlock(String lockKey, String lockValue) {
    try {
      if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {
        return redisTemplate.delete(lockKey);
      }
    } catch (Exception e) {
      System.out.println("释放锁异常");
    }
    return false;
  }

  1. 基于zookeeper实现分布式锁
    按照上面三部,我们来写写实现思路:

第一步:获取锁,多个JVM连接zk去创建临时节点,临时节点是不能重复的,谁创建了临时节点,则表明谁获取了锁,没有获取锁的JVM则继续等待

第二步:等待获取,我们可以设置等待时间,在时间内则继续等待,否则获取锁失败,也可以一直等待,这里是一直等待

第三步:释放锁,业务逻辑处理完成后,断开客户端与zk的连接,则临时节点会被删除,相当于释放了锁,则其他JVM可以继续去创建临时节点(锁)

上代码:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

**

## 源码:

**
引包:
    <dependency>
      <groupId>com.101tec</groupId>
      <artifactId>zkclient</artifactId>
      <version>0.11</version>
      <exclusions>
        <exclusion>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
        <exclusion>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

/**
 * 
 * 创建zk连接
 */
public class ZookeeperLink {

  private static ZkClient zkClient = null;

  public static ZkClient createZkClient() {
    if (zkClient != null) {
      zkClient.close();
    }
    zkClient = new ZkClient("127.0.0.1:2181", 5000);
    return zkClient;
  }
}

/**
 * 
 * 获取锁和释放锁的类
 */
public class ZkLock {

  /**
   * 1.获取锁:谁创建了临时节点谁就获取了锁 2.释放锁:删除掉临时节点就释放了锁 3.等待:未获取锁等待重试循环一直获取锁
   */
  private static final String PATH = "/zkKey";

  private CountDownLatch countDownLatch = new CountDownLatch(1);

  /**
   * 获取锁
   */
  public boolean getLock() {
    // 调用获取锁方法,成功则返回true
    if (tryLock()) {
      System.out.println("获取到了锁");
      return true;
    } else {
      // 等待获取锁,并进行事件监听,只要锁被释放了,就立即去重新获取锁,调用getLock()
      waitLock();
      return getLock();

    }
  }

  /**
   * 尝试创建节点
   *
   * @return
   */
  private boolean tryLock() {
    try {
      ZookeeperLink.createZkClient().createEphemeral(PATH);
      return true;
    } catch (Exception e) {
      return false;
    }
  }

  /**
   * 等待获取锁
   *
   */
  private void waitLock() {
    IZkDataListener iZkDataListener = new IZkDataListener() {
      @Override
      public void handleDataChange(String s, Object o) throws Exception {

      }

      @Override
      public void handleDataDeleted(String s) throws Exception {
        countDownLatch.countDown();
      }
    };
    // 做事件监听,节点被删除会立即通知
    ZookeeperLink.createZkClient().subscribeDataChanges(PATH, iZkDataListener);
    try {
      countDownLatch.await();
      // 取消事件监听
      ZookeeperLink.createZkClient().unsubscribeDataChanges(PATH, iZkDataListener);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  /**
   * 释放锁
   */
  public void unLock() {
    ZookeeperLink.createZkClient().close();
  }
}

以上就是分布式锁的实现思路,这只是其中的一种,获取不到锁就等待,也可以用另外一种思路,比如重试机制,只需要把上面的代码稍稍修改一下,比如,重试3次,每次间隔3秒钟,实现思路就是,在获取锁的方法上加个for循环,循环三次,每次循环完线程休眠3秒,就可以实现重试机制了。

最后如果redis不了解原理的可以参考我的另一篇文章:
https://blog.csdn.net/weixin_43727615/article/details/115548679

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值