一、使用场景
- 集群情况下的定时任务
- 抢单
- 库存扣减
- 幂等性场景
- 原逻辑
java
/**
* 抢购优惠券
* @throws InterruptedException
*/
public void rushToPurchase() throws InterruptedException {
// 获取优惠券数量
Integer num=(Integer)redisTemplate.opsForValue().get("num");
// 判断是否抢完
if(null==num || num<=0){
throw new RuntimeException("优惠券已抢完");
}
// 优惠券数量减一,说明抢到了优惠券
num = snum-1;
// 重新设罡凳惠券的数最
redisTemplate.opsForValue().set("num",num);
}
- 并发产生的后果:
导致库存超卖
Synchronized
互斥锁
shell
public void rushToPurchase() throws InterruptedException {
Synchronized(this) {
// 获取优惠券数量
Integer num=(Integer)redisTemplate.opsForValue().get("num");
// 判断是否抢完
if(null == num || num<=0){
throw new RuntimeException("优惠券已抢完");
}
// 优惠券数量减一,说明抢到了优惠券
num = snum-1;
// 重新设罡凳惠券的数最
redisTemplate.opsForValue().set("num",num);
}
}
- 流程图如下:
单体项目可以支持,但多台确有问题
二、SETNX
- Redis实现分布式锁主要利用Redis的
setnx命令
。 setnx
: 是SET if not exists(如果不存在,则 SET)
的简写
shell
# 添加锁, NX 是互斥, EX设置超时时间
$ SET lock value NX EX 10
shell
# 释放锁
$ DEL key
四、Redisson 红锁
- 当前案例就存在2个服务,2个线程同时持有
相同的锁
,无法保证一致性问题
。
RedLock(红锁)
: 不能只在一个redis实例上创建锁,应该是在多个redis实例
上创建锁(n/2+1)
,避免在一个redis实例上加锁
。
例如: 3台Redis, 创建锁(3/2+1)约等于 2个节点(超过Redis 一半)。
缺点
注意: redis 官方不建议使用Redisson 红锁的使用。
- 实现复杂
- 性能差
- 运维繁琐
五、面试问题
回答:
- 我们在业务中使用【分布式锁使用的场景】是
抢券
- 我们当使用的
redisson实现的分布式锁
,底层是setnx
和lua脚本(主要作用是保证命令原子性)
回答:
- 在redisson的分布式锁中,提供了一个
WatchDog(看门狗)
,一个线程获取锁成功以后。 WatchDog
会给持有锁的线程
=>续期(默认是每隔10秒续期一次)
。
回答:
- 可以
重入
,多个锁重入
需要判断 =>是否是当前线程
- 在redis中进行存储的时候, 通过的
hash结构
来存储 =>线程信息(唯一标识)
和重入的次数
.
回答:
- 不能解决,但是可以
使用redisson提供的红锁来解决
,但是这样的话,性能就太低了
。 - 如果业务中非要保证数据的
强一致性
,建议采用zookeeper实现的分布式锁
。