Redis分布式锁实现

一 分布式锁核心知识介绍和注意事项

  • 加锁 SETNX key value
    setnx 的含义就是 SET if Not Exists,有两个参数 setnx(key, value),该方法是原子性操作
    如果 key 不存在,则设置当前 key 成功,返回 1;
    如果当前 key 已经存在,则设置当前 key 失败,返回 0
  • 解锁 del (key)
    得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入,调用 del(key)
  • 配置锁超时 expire (key,30s)
    客户端奔溃或者网络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放

 二 注意点

  • 多个命令之间不是原子性操作,如setnxexpire之间,如果setnx成功,但是expire失败,且宕机了,则这个资源就是死锁
    使用原子命令:设置和配置过期时间  setnx / setex
    如: set key 1 ex 30 nx
    java里面 redisTemplate.opsForValue().setIfAbsent("seckill_1","success",30,TimeUnit.MILLISECONDS)
    

     

  • 业务超时,存在其他线程勿删,key 30秒过期,假如线程A执行很慢超过30秒,则key就被释放了,其他线程B就得到了锁,这个时候线程A执行完成,而B还没执行完成,结果就是线程A删除了线程B加的锁

    可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁, 那 value 应该是存当前线程的标识或者uuid
  • 加锁+配置过期时间:保证原子性操作
  • 解锁: 防止误删除、也要保证原子性操作
/**
* 原生分布式锁 开始
* 1、原子加锁 设置过期时间,防止宕机死锁
* 2、原子解锁:需要判断是不是自己的锁
*/
@RestController
@RequestMapping("/api/v1/coupon")
public class CouponController {
​
    @Autowired
    private StringRedisTemplate redisTemplate;
​
​
    @GetMapping("add")
    public JsonData saveCoupon(@RequestParam(value = "coupon_id",required = true) int couponId){
​
        //防止其他线程误删
        String uuid = UUID.randomUUID().toString();
​
        String lockKey = "lock:coupon:"+couponId;
​
        lock(couponId,uuid,lockKey);
​
        return JsonData.buildSuccess();
​
    }
​
​
    private void lock(int couponId,String uuid,String lockKey){
​
​
        //lua脚本
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
​
        Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(lockKey,uuid,Duration.ofSeconds(30));
        System.out.println(uuid+"加锁状态:"+nativeLock);
        if(nativeLock){
            //加锁成功
​
            try{
                //TODO 做相关业务逻辑
                TimeUnit.SECONDS.sleep(10L);
​
            } catch (InterruptedException e) {
​
            } finally {
                //解锁
                Long result = redisTemplate.execute( new DefaultRedisScript<>(script,Long.class),Arrays.asList(lockKey),uuid);
                System.out.println("解锁状态:"+result);
​
            }
​
        }else {
            //自旋操作
            try {
                System.out.println("加锁失败,睡眠5秒 进行自旋");
                TimeUnit.MILLISECONDS.sleep(5000);
            } catch (InterruptedException e) { }
​
            //睡眠一会再尝试获取锁
            lock(couponId,uuid,lockKey);
        }
    }
​
​
}

遗留一个问题,锁的过期时间,如何实现锁的自动续期 或者 避免业务执行时间过长,锁过期了?

  • 原生方式的话,一般把锁的过期时间设置久一点,比如10分钟时间
     

    原生代码+redis实现分布式锁使用比较复杂,且有些锁续期问题更难处理

  • 延伸出框架 官方推荐方式:https://redis.io/topics/distlock
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值