彻底掌握分布式锁lua脚本+redis原生代码编写

DBC 1.6K 0
温馨提示

看一下简洁的代码

/**
* 原生分布式锁 开始
* 1、原子加锁 设置过期时间,防止宕机死锁
* 2、原子解锁:需要判断是不是自己的锁
*/
String uuid = CommonUtil.generateUUID();
String lockKey = "lock:coupon:"+couponId;
Boolean nativeLock=redisTemplate.opsForValue().setIfAbsent(lockKey,uuid,Duration.ofSeconds(30));
    if(nativeLock){
      //加锁成功
      log.info("加锁:{}",nativeLock);
      try {
           //执行业务  TODO
        }finally {
           String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
​
                Integer result = redisTemplate.execute(new DefaultRedisScript<>(script, Integer.class), Arrays.asList(lockKey), uuid);
                log.info("解锁:{}",result);
            }
​
        }else {
            //加锁失败,睡眠100毫秒,自旋重试
            try {
                TimeUnit.MILLISECONDS.sleep(100L);
            } catch (InterruptedException e) { }
            return addCoupon( couponId, couponCategory);
        }
        //原生分布式锁 结束

注意一下,在有些版本下,使用上面的写法会报错,我们可以尝试这种

        // 加锁10秒过期
        Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(key, snowflakeId, Duration.ofSeconds(10));

        if (nativeLock) {

    }
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
                Object result = redisTemplate.execute(redisScript, Collections.singletonList(key), snowflakeId);

较完整做法

        // 加锁10秒过期
        String snowflakeId = snowflakeIdWorker.nextId() + "";
        Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(lockKey, snowflakeId, Duration.ofSeconds(10));
        if (nativeLock) {
            //加锁成功
            try {
                CopyOnWriteArrayList<ProjectPropertyVO> resultList = this.getList(division,year);
                redisTemplate.opsForValue().set(key, resultList, 5, TimeUnit.SECONDS);
                redisTemplate.opsForValue().set(keyBak, resultList, 20, TimeUnit.SECONDS);
            }finally {
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
                Object result = redisTemplate.execute(redisScript, Collections.singletonList(key), snowflakeId);

            }
        }
BUG式的实现,也可以看看
 /**
     * 领劵接口
     * 1、获取优惠券是否存在
     * 2、校验优惠券是否可以领取:时间、库存、超过限制
     * 3、扣减库存
     * 4、保存领劵记录
     * <p>
     * 始终要记得,羊毛党思维很厉害,社会工程学 应用的很厉害
     *
     * @param couponId
     * @param category
     * @return
     */
    @Override
    public JsonData addCoupon(long couponId, CouponCategoryEnum category) {


        String uuid = CommonUtil.generateUUID();
        String lockKey = "lock:coupon:" + couponId;
        //避免锁过期,一般配久一点
        Boolean lockFlag = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, Duration.ofMinutes(10));
        if (lockFlag) {
            try {

                //执行业务逻辑  TODO
                log.info("加锁成功:{}", couponId);

                LoginUser loginUser = LoginInterceptor.threadLocal.get();

                CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>()
                        .eq("id", couponId)
                        .eq("category", category.name()));


                //优惠券是否可以领取
                this.checkCoupon(couponDO, loginUser.getId());


                //构建领劵记录
                CouponRecordDO couponRecordDO = new CouponRecordDO();
                BeanUtils.copyProperties(couponDO, couponRecordDO);
                couponRecordDO.setCreateTime(new Date());
                couponRecordDO.setUseState(CouponStateEnum.NEW.name());
                couponRecordDO.setUserId(loginUser.getId());
                couponRecordDO.setUserName(loginUser.getName());
                couponRecordDO.setCouponId(couponId);
                couponRecordDO.setId(null);


                //扣减库存  TODO
                int rows = couponMapper.reduceStock(couponId);

                if (rows == 1) {
                    //库存扣减成功才保存记录
                    couponRecordMapper.insert(couponRecordDO);

                } else {
                    log.warn("发放优惠券失败:{},用户:{}", couponDO, loginUser);


                    throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
                }
            } finally {

                //解锁
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                Integer result = redisTemplate.execute(new DefaultRedisScript<>(script, Integer.class), Arrays.asList(lockKey), uuid);
                log.info("解锁:{}", result);
            }

        } else {
            //加锁失败
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                log.error("自旋失败");
            }
            addCoupon(couponId, category);
        }

        return JsonData.buildSuccess();
    }

发表评论 取消回复
表情 图片 链接 代码

分享