/**
* 原生分布式锁 开始
* 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,转载请注明。