分布式锁场景
接口幂等性校验
抢优惠券
互联网秒杀
Redis分布式锁实现
setnx
原子加
Redisson分布式锁实现原理
redis分布式锁用过的有好几种,我写几个比较常见的版本,这样新手也比较容易看懂
一般的代码:
@RequestMapping("/lock")
public String deductStock() {
String lockKey = "product_100";
String clientId = UUID.randomUUID().toString();
try {
//加锁
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 5, TimeUnit.SECONDS);
if (!result) {
return "有锁了,等会";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
stringRedisTemplate.delete(lockKey);
}
return "end";
}
但是在真正的高并发场景下,上面的代码还是可以再完善的,
设想一下下面的场景:
A加了锁,锁key是任务字段+货物id,锁5秒过期,
这个时候,在解锁之前,A线程卡了10秒,但是锁超时时间设置了5秒,
5秒后A加的锁就超时,第9.99999999999999秒的时候,B加锁了。
A第10.000000000001秒,这个时候不卡了,走到了解锁逻辑,就把B锁拆了。
初级改进代码:
谁加锁谁解锁
@RequestMapping("/lock")
public String deductStock() {
String lockKey = "product_100";
String clientId = UUID.randomUUID().toString();//这里的uuid可以用了
try {
//加锁
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
if (!result) {
return "有锁了,等会";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
//谁加的锁谁解锁,但是注意,这里不是原子性操作
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
但解锁步骤不是原子性操作。
优化后代码:
@Autowired
private Redisson redisson;
@RequestMapping("/lock")
public String deductStock() {
String lockKey = "product_8848";
//拿到锁
RLock redissonLock = redisson.getLock(lockKey);
try {
//加锁,实现锁续命功能,默认30秒超时,每10秒检查是否持有锁,持有则延长锁时间
redissonLock.lock();
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
redissonLock.unlock();
}
return "end";
}
底层也是lua脚本实现,设置了key和线程id
时间除3
看门狗的时间
不推荐使用下面的锁