什么是Redis分布式锁

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

创新互联建站长期为近1000家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为阿克塞哈萨克族自治企业提供专业的网站制作、成都网站设计阿克塞哈萨克族自治网站改版等技术服务。拥有十载丰富建站经验和众多成功案例,为您定制开发。

什么是分布式锁

在分布式系统中,有些业务场景会用到分布式锁,实现分布式锁的方式有很多,本篇主要讲根据Redis如何来实现。

首先我们要知道分布式锁的一些基本特点:

  1. 互斥性:只有一个客户端可以持有锁

  2. 不会产生死锁:即使持有锁的客户端崩溃,也能保证后续其他客户端可以获得锁

  3. 只有持有这把锁的客户端才能解锁

下边我们通过几个例子来说明分布式锁为什么需要以上3个特点。

不加客户端校验解锁
/**
 * 使用jedis客户端实现分布式锁
 * @Author: maomao
 * @Date: 2021-04-27 08:42
 */
public class DistLock {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param value 值
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String value, String requestId, int expireTime) {
        // set支持多个参数 NX(not exist) XX(exist) EX(seconds) PX(million seconds)
        String result = jedis.set(lockKey, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    /**
     * 直接删除解锁,未判断客户端ID,会导致其他客户端把锁释放
     * @param jedis
     * @param lockKey
     */
    public static void releaseLock1(Jedis jedis, String lockKey) {
        jedis.del(lockKey);
    }
}

上边代码是一个不验证客户端的例子,加锁是没有问题的,但在解锁时会有很大的问题。

什么是Redis分布式锁

通过上图可以看到,因为没有校验客户端逻辑,Thread B可以直接解锁,而Thread A程序还未执行完,但已被解锁,造成锁失效。如果此时有其他客户端加锁是可以加锁成功的。

那我们可以在代码中增加一个客户端校验不就可以了?

加客户端校验解锁
   /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识-修改此处为客户端唯一标致
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        // set支持多个参数 NX(not exist) XX(exist) EX(seconds) PX(million seconds)
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

   /**
     * 增加锁判断,但因判断与删除不是原子操作,在并发场景时,会导致错误删除
     * @param jedis
     * @param lockKey
     * @param requestId
     */
    public static void releaseLock2(Jedis jedis, String lockKey, String requestId) {
        // 判断加锁与解锁是不是同一个客户端
        if (requestId.equals(jedis.get(lockKey))) {
            // 若在此时,这把锁突然不是这个客户端的,则会误解锁
            // 两个操作不能保证原子性
            jedis.del(lockKey);
        }
    }

在解锁代码中可以看到,我们也增加了客户端标志校验应该可以解决客户端校验问题了吧?其实并没有,我们要知道对redis来说,每个命令都是原子的,你的get与del方法是两个命令,无法保证原子操作。也就是我们多线程中常见的i++;操作,其实他是由3个操作执行。

那我们如何确保get与del的原子操作呢?我们可以使用lua脚本来实现。上述代码我们可以调整为一个lua脚本。

   /**
     * 释放分布式锁,使用lua脚本删除,可确保判断与删除的原子操作
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

通过增加客户端校验与解锁的原子性就可以实现安全的解锁。

什么是Redis分布式锁

有了上边的方式是不是就可以确保分布式锁的全部问题了?并不是,还有一种场景没有考虑到。

程序执行时间超出锁的过期时间

如果我们的加锁程序执行时间超出锁过期时间时,就会导致分布式锁失效。此时其他客户端是可以获得到锁的。如下图:

什么是Redis分布式锁

那么这种问题如何解决呢?

Redission

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid),提供了分布式和可扩展的Java数据结构,比如分布式对象,分布式集合(Map、List、Queue、Set),分布式锁等等功能,不需要自己去运行一个服务实现。

什么是Redis分布式锁

Redisson官网

Redisson Git地址

Redission是由一个中国人与俄罗斯人共同发起的,所以中文文档比较详细。

使用Redission实现分布式锁

使用Redission可以很简单的实现分布式锁,代码如下:

public static void main(String[] args) throws InterruptedException {
        //设定锁标志
        //会在redis中创建一个Hash,Key是客户端UUID,value是锁重入次数
        RLock rLock = redissonClient.getLock("lockKey");
        // 最多等待100秒、上锁10s以后自动解锁
        if(rLock.tryLock(100,10, TimeUnit.SECONDS)){
            System.out.println("获取锁成功,此时可以查看redis中的数据!");
        }
        //线程等待后可在redis中查到
        Thread.sleep(20000);
        rLock.unlock();
}

什么是Redis分布式锁

Redission不只可以实现独占锁,还可以实现如:可重入锁、公平锁、联锁、红锁、读写锁等等。

什么是Redis分布式锁

redission实现分布式锁的逻辑基本与上边我们讲的原理差不多,它还解决了我们最后一个问题,程序执行时间超出锁过期时间的问题。

他使用了一个《看门狗》的概念来实现自动续期。默认最大续期时间30s,也就是说如果业务超出30秒还未执行会自动解锁。

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


当前文章:什么是Redis分布式锁
本文路径:http://scyanting.com/article/pjjgce.html