/** * 原生分布式锁 开始 * 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(); }
本文作者为DBC,转载请注明。