一、使用场景
- 集群情况下的定时任务
- 抢单
- 库存扣减
- 幂等性场景
- 原逻辑
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 10shell
# 释放锁
$ 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实现的分布式锁。
