面试官:Redis如何实现分布式锁

12 篇文章 0 订阅
2 篇文章 0 订阅
本文详细介绍了Redis实现分布式锁的过程,包括代码实现方式(setnx配合过期时间、UUID唯一值和lua脚本保证原子性),以及遇到的问题和解决策略。
摘要由CSDN通过智能技术生成

大家好,我是程序员阿药。今天和大家分享的是一个面试题,Redis如何实现分布式锁。话不多说,发车!

获取Redis分布式锁的流程

多个客户端同时竞争锁,当其中一个客户端获得锁后开始执行业务逻辑,其他未得到锁的客户端等待重试,待拥有锁的客户端执行完业务逻辑后释放锁,然后其他等待重试的客户端中的一个即可获得。如图:

图片

代码实现(一)

public void testLock(){
    //1获取锁,setnx
    Boolean lock = redisTemplate.opsForValue()
            .setIfAbsent("lock", "value");
    //2获取锁成功、查询num的值(执行业务逻辑)
    if(lock){
        Object value = redisTemplate.opsForValue().get("num");
        //2.1判断num为空return
        if(null == value){
            return;
        }
        //2.2有值就转成成int
        int num = Integer.parseInt(value+"");
        //2.3把redis的num加1
        redisTemplate.opsForValue().set("num", ++num);
        //2.4释放锁,del
        redisTemplate.delete("lock");
    }else{
        //3获取锁失败、每隔0.1秒再获取
        try {
            Thread.sleep(100);
            testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

采用代码实现(一)带来的问题和解决办法

问题:假设某个客户端刚好获得锁开始执行业务逻辑,但是执行时出现异常导致锁无法释放。

解决:给锁设置过期时间,自动释放锁。设置过期时间的方式如下。

  1. 通过expire设置,但是缺乏原子性,如果在setnx和expire之间出现异常,锁也无法释放。

  2. 在set时设置过期时间,推荐使用这种方式。

图片

代码实现(二)

public void testLock(){
    //1获取锁,setnx
    Boolean lock = redisTemplate.opsForValue()
            .setIfAbsent("lock", "value", 3, TimeUnit.SECONDS);
    //2获取锁成功、查询num的值(执行业务逻辑)
    if(lock){
        ......
        //2.4释放锁,del
        redisTemplate.delete("lock");
    }else{
        //3获取锁失败、每隔0.1秒再获取
        ......
    }
}

 采用代码实现(二)带来的问题和解决办法

问题:如果客户端1的业务逻辑没有执行完,但是锁已经过期自动释放;然后客户端2获取到锁,当客户端1执行释放锁时,此时释放的是客户端2的锁(因为锁一直都是同一把锁)。

解决:setnx设置锁时,设置一个指定的唯一值uuid,释放锁之前获取这个值,判断是不是自己的锁(客户端1和客户端2中的uuid是不同的)。

代码实现(三)

public void testLock(){
    String uuid = UUID.randomUUID().toString();
    //1获取锁,setnx
    Boolean lock = redisTemplate.opsForValue()
            .setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
    //2获取锁成功、查询num的值(执行业务逻辑)
    if(lock){
        ......
        //2.4释放锁,del
        //判断比较uuid值是否一样
        String lockUuid = (String) redisTemplate.opsForValue().get("lock");
        if (null == lockUuid) {
            return;
        }
        if (lockUuid.equals(uuid)) {
            //是自己的锁,再释放
            redisTemplate.delete("lock");
        }
    }else{
        //3获取锁失败、每隔0.1秒再获取
        ......
    }
}

采用代码实现(三)带来的问题和解决办法

问题:如果客户端1执行完对uuid的判断后,刚刚要删除锁但是这个时候锁自动释放;然后客户端2获得了锁,此时客户端1将会删除客户端2的锁(无法保证删除的原子性)。

解决:通过LUA脚本保证删除的原子性。

代码实现(四)

public void testLockLua() {
    String uuid = UUID.randomUUID().toString();
    // 1.获取锁
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
    // 2.获取锁成功、查询num的值(执行业务逻辑)
    if (lock) {
        ......
        // 2.4使用lua脚本来释放锁,del
        // 定义lua脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        // 使用redis执行lua执行
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        // 设置一下返回值类型 为Long
        // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
        // 那么返回字符串与0 会有发生错误。
        redisScript.setResultType(Long.class);
        // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
        redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
    } else {
        // 3.获取锁失败、每隔0.1秒再获取
        ......
    }
}

到此为止,Redis实现分布式锁结束。如果哪里写的不好希望大家批评指正。如果有帮助的话,希望各位小伙伴可以点赞收藏支持一下,我们下篇再见。

  • 27
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值