【108期】如何利用 redis 分布式锁,解决秒杀场景下的订单超卖问题?

最后总结

搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析

最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

(integer) 1

127.0.0.1:6379> setnx lock-num 1  – 其他客户端获取不到锁

(integer) 0

死锁: 如果加了锁,但是没有释放,就会导致死锁,其他客户端一直获取不到锁。

使用expire为锁key添加时间限定,到时间不释放,放弃锁

由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。

  • expire lock-key second

  • pexpire lock-key milliseconds

127.0.0.1:6379> set name 123

OK

127.0.0.1:6379> setnx lock-name 1 – 锁的名称key

(integer) 1

127.0.0.1:6379> expire lock-name 20 – 使用expire为锁key添加时间限定

(integer) 1

127.0.0.1:6379> get name

“123”

GETSET key value

将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

redis> GETSET db mongodb    # 没有旧值,返回 nil

(nil)

redis> GET db

“mongodb”

redis> GETSET db redis      # 返回旧值 mongodb

“mongodb”

redis> GET db

“redis”

2.2 RedisLock

@Component

@Slf4j

public class RedisLock {

@Autowired

private StringRedisTemplate redisTemplate;

/**

* 加锁

* @param key:productId

* @param value 当前时间+超时时间

*/

public boolean lock(String key, String value) {

//setnx----对应方法 setIfAbsent(key, value),如果可以加锁返回true,不可以加锁返回false

if(redisTemplate.opsForValue().setIfAbsent(key, value)) {

return true;

}

//下面这段代码时为了解决可能出现的死锁情况

String currentValue = redisTemplate.opsForValue().get(key);

//如果锁过期

if (!StringUtils.isEmpty(currentValue)

&& Long.parseLong(currentValue) < System.currentTimeMillis()) {

//获取上一个锁的时间:重新设置锁的过期时间value,并返回上一个过期时间

String oldValue = redisTemplate.opsForValue().getAndSet(key, value);

//currentValue =2020-12-28,两个线程的value=2020-12-29,只会有一个线程拿到锁

if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {

return true;

}

}

return false;

}

//解锁

public void unlock(String key, String value) {

try {

String currentValue = redisTemplate.opsForValue().get(key);

if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {

redisTemplate.opsForValue().getOperations().delete(key);

}

}catch (Exception e) {

log.error(“【redis分布式锁】解锁异常, {}”, e);

}

}

}

2.3 将redis分布式锁应用于秒杀业务

@Override

public void orderProductMockDiffUser(String productId) {

//加锁

//锁的过期时间为当前时间+过期时长

long time = System.currentTimeMillis()+TIMEOUT;

if(!redisLock.lock(productId,String.valueOf(time))){

throw new SellException(101,“人太多,稍后再来”);

}

//1.查询该商品库存,为0则活动结束。

int stockNum = stock.get(productId);

if(stockNum == 0) {

throw new SellException(100,“活动结束”);

}else {

//2.下单(模拟不同用户openid不同)

orders.put(KeyUtil.genUniqueKey(),productId);

//3.减库存

stockNum =stockNum-1;

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

//4.更新库存

stock.put(productId,stockNum);

}

//解锁

redisLock.unlock(productId,String.valueOf(time));

}

2.4 分析RedisLock

重点分析加锁逻辑,有两个逻辑需要考虑:

public boolean lock(String key, String value) {

if(redisTemplate.opsForValue().setIfAbsent(key, value)) {

return true;

}

//下面的代码是为了解决可能出现的死锁的情况????

String currentValue = redisTemplate.opsForValue().get(key);

if (!StringUtils.isEmpty(currentValue)

&& Long.parseLong(currentValue) < System.currentTimeMillis()) {

//下面这个逻辑又怎么理解????

String oldValue = redisTemplate.opsForValue().getAndSet(key, value);

if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {

return true;

}

}

return false;

}

为了解决可能出现的死锁的情况????

//秒杀业务方法

@Override

public void orderProductMockDiffUser(String productId) {

//加锁

//锁的过期时间为当前时间+过期时长

long time = System.currentTimeMillis()+TIMEOUT;

if(!redisLock.lock(productId,String.valueOf(time))){

throw new SellException(101,“人太多,稍后再来”);

}

//1.查询该商品库存,为0则活动结束。

int stockNum = stock.get(productId);

if(stockNum == 0) {

throw new SellException(100,“活动结束”);

}else {

//2.下单

orders.put(KeyUtil.genUniqueKey(),productId);

//3.减库存

stockNum =stockNum-1;

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

//4.更新库存

stock.put(productId,stockNum);

}

//解锁

redisLock.unlock(productId,String.valueOf(time));

}

假如我们将中间那段逻辑去掉会出现声明情况???

public boolean lock(String key, String value) {

if(redisTemplate.opsForValue().setIfAbsent(key, value)) {

return true;

}

return false;

}

① 线程A执行秒杀的业务逻辑方法,并对这个方法加了锁,key=proeuctId,value=加锁时间+过期时长,然后开始执行下单----》减库存-----》更新库存等操作,如果在执行的过程中,这段代码发生了异常,那么线程A是不会释放锁的,导致其他线程都无法获取到锁导致死锁的产生,所以下面的逻辑是很有必要加的,即如果当前时间晚于锁的过期时间,那么就会向下走if()条件:

//下面的代码是为了解决可能出现的死锁的情况????

String currentValue = redisTemplate.opsForValue().get(key);

if (!StringUtils.isEmpty(currentValue)

&& Long.parseLong(currentValue) < System.currentTimeMillis()) {

//下面这个逻辑又怎么理解????

String oldValue = redisTemplate.opsForValue().getAndSet(key, value);

if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {

return true;

}

}

② 可是下面的if()条件怎么理解?

currentValue=2020-12-18

假如现在两个线程A和B同时执行lock()方法,也就是这两个线程的value是完全相同的,都为value=2020-12-19,而他们都执行 String oldValue = redisTemplate.opsForValue().getAndSet(key, value);,会有一个先执行一个后执行:

假如线程A先执行,返回的oldValue=2020-12-18,同时设置value = 2020-12-19,由于oldvalue=currentValue返回true,即A线程加了锁;

此时B线程继续执行 ,返回的oldValue=2020-12-19,oldvalue!=currentValue,返回false,加锁失败。

所以这段代码的逻辑是只会让一个线程加锁。推荐公众号Java精选,回复Java面试,获取Redis相关面试题资料。

public boolean lock(String key, String value) {

if(redisTemplate.opsForValue().setIfAbsent(key, value)) {

return true;

}

String currentValue = redisTemplate.opsForValue().get(key);

if (!StringUtils.isEmpty(currentValue)

&& Long.parseLong(currentValue) < System.currentTimeMillis()) {

//下面这个逻辑又怎么理解????

String oldValue = redisTemplate.opsForValue().getAndSet(key, value);

if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {

return true;

}

}

return false;

}

我的面试宝典:一线互联网大厂Java核心面试题库

以下是我个人的一些做法,希望可以给各位提供一些帮助:

整理了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!

image

283页的Java进阶核心pdf文档

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

image

还有源码相关的阅读学习

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

df文档**

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

[外链图片转存中…(img-C0Mu9kfF-1715476125892)]

还有源码相关的阅读学习

[外链图片转存中…(img-cfZUzIkY-1715476125892)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值