分布式锁的原理及Redis怎么实现分布式锁

发布时间:2023-01-31 10:12:29 作者:iii
来源:亿速云 阅读:138

这篇文章主要介绍“分布式锁的原理及Redis怎么实现分布式锁”,在日常操作中,相信很多人在分布式锁的原理及Redis怎么实现分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”分布式锁的原理及Redis怎么实现分布式锁”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

一、分布式锁基本原理

分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。

分布式锁应该满足的条件:

常见的分布式锁有三种:

分布式锁的原理及Redis怎么实现分布式锁

二、基于Redis实现分布式锁

实现分布式锁时需要实现的两个基本方法:

基于Redis实现分布式锁原理:

SET resource_name my_random_value NX PX 30000

利用NX的原子性,多个线程并发时,只有一个线程可以设置成功,设置成功表示获得锁,可以执行后续的业务处理;如果出现异常,过了锁的有效期,锁自动释放;

版本一

1、定义ILock接口

public interface ILock extends AutoCloseable {
   /**
    * 尝试获取锁
    *
    * @param timeoutSec 锁持有的超时时间,过期后自动释放
    * @return true代表获取锁成功;false代表获取锁失败
    */
   boolean tryLock(long timeoutSec);

   /**
    * 释放锁
    * @return
    */
   void unLock();
}

2、基于Redis实现分布式锁—RedisLock

public class SimpleRedisLock {
   private final StringRedisTemplate stringRedisTemplate;
   private final String name;

   public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
       this.stringRedisTemplate = stringRedisTemplate;
       this.name = name;
   }

   private static final String KEY_PREFIX = "lock:";

   @Override
   public boolean tryLock(long timeoutSec) {
       //获取线程标识
       String threadId = Thread.currentThread().getId();
       //获取锁
       Boolean success = stringRedisTemplate.opsForValue()
               .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
       return Boolean.TRUE.equals(success);
   }

   @Override
   public void unLock() {
       //通过del删除锁
       stringRedisTemplate.delete(KEY_PREFIX + name);
   }

   @Override
   public void close() {
       unLock();
   }
}

锁误删问题

问题说明:

持有锁的线程1在锁的内部出现了阻塞,这时锁超时自动释放,这时线程2尝试获得锁,然后线程2在持有锁执行过程中,线程1反应过来,继续执行,走到了删除锁逻辑,此时就会把本应该属于线程2的锁进行删除,这就是锁误删的情况。

解决方案:

在存入锁时,放入自己线程的标识,在删除锁时,判断当前这把锁的标识是不是自己存入的,如果是,则进行删除,如果不是,则不进行删除。

版本二:解决锁误删问题

public class SimpleRedisLock {
   private final StringRedisTemplate stringRedisTemplate;
   private final String name;

   public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
       this.stringRedisTemplate = stringRedisTemplate;
       this.name = name;
   }

   private static final String KEY_PREFIX = "lock:";
   private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

   @Override
   public boolean tryLock(long timeoutSec) {
       //获取线程标识
       String threadId = ID_PREFIX + Thread.currentThread().getId();
       //获取锁
       Boolean success = stringRedisTemplate.opsForValue()
               .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
       return Boolean.TRUE.equals(success);
   }

   @Override
   public void unLock() {
       // 获取线程标示
       String threadId = ID_PREFIX + Thread.currentThread().getId();
       // 获取锁中的标示
       String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
       // 判断标示是否一致
       if(threadId.equals(id)) {
           // 释放锁
           stringRedisTemplate.delete(KEY_PREFIX + name);
       }
   }

   @Override
   public void close() {
       unLock();
   }
}

锁释放的原子性问题

问题分析:

上述释放锁的代码依然存在锁误删问题,当线程1获取锁中的线程标识,并根据标识判断是自己的锁,这时锁到期自动释放,恰好线程2尝试获取锁,并拿到了锁,此时线程1依然执行释放锁的操作,就导致误删了线程2持有的锁。

原因在于,由java代码实现的释放锁流程不是原子操作,存在线程安全问题。

解决方案:

Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,可以确保多条命令执行时的原子性。

版本三:调用Lua脚本改造分布式锁

public class SimpleRedisLock implements ILock {
   private final StringRedisTemplate stringRedisTemplate;
   private final String name;

   public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
       this.stringRedisTemplate = stringRedisTemplate;
       this.name = name;
   }

   private static final String KEY_PREFIX = "lock:";
   private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

   @Override
   public boolean tryLock(long timeoutSec) {
       //获取线程标识
       String threadId = ID_PREFIX + Thread.currentThread().getId();
       //获取锁
       Boolean success = stringRedisTemplate.opsForValue()
               .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
       return Boolean.TRUE.equals(success);
   }

   @Override
   public void unLock() {
       String script = "if redis.call("get",KEYS[1]) == ARGV[1] then\n" +
               " return redis.call("del",KEYS[1])\n" +
               "else\n" +
               " return 0\n" +
               "end";
       //通过执行lua脚本实现锁删除,可以校验随机值
       RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
       stringRedisTemplate.execute(redisScript,
               Collections.singletonList(KEY_PREFIX + name),
               ID_PREFIX + Thread.currentThread().getId());
   }

   @Override
   public void close() {
       unLock();
   }
}

到此,关于“分布式锁的原理及Redis怎么实现分布式锁”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. RedisLock分布式redis锁
  2. Redis如何实现分布式锁

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

redis

上一篇:php无法上传中文文件如何解决

下一篇:centos php如何安装opcache

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》