【解决案例】使用Redis解决秒杀过程中超卖问题
最近打算写个专栏专门记录下遇到的一些案例解决,也算是记录一些成长的经验吧。本篇文章背景是在完善商城秒杀系统时发现秒杀时会出现超卖问题,然后在参考一系列解决方案后决定采用Redis记录库存数目来解决,但是在其中还是出现了一些问题。1. 初解出现问题的代码块://获取库存数目int stock = Integer.parseInt(redisTemplate.opsForValue().get("st
最近打算写个专栏专门记录下遇到的一些案例解决,也算是记录一些成长的经验吧。
本篇文章背景是在完善商城秒杀系统时发现秒杀时会出现超卖问题,然后在参考一系列解决方案后决定采用Redis
记录库存数目来解决,但是在其中还是出现了一些问题。
1. 初解
出现问题的代码块:
//获取库存数目
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
//库存数目大于零则减库存
if(stock > 0){
int finalStock = stock - 1;
//更新库存
redisTemplate.opsForValue().set("stock",Integer.toString(finalStock));
}
在之前学习Redis
时,知道Redis
是单线程运行的,所以先入为主的认为不会出现线程不安全问题,但是思考一下能发现,实际操作库存时,Redis
只有获取与修改操作,两线程在同时获取库存后对库存操作则会出现同时卖出但是库存只记录其一的超卖现象。
而如果需要利用单线程这一优势,则需要操作库存时是要在redis
中对库存设置自增与自减,但是在获取库存时还是会出现上面逻辑判断错误的问题,到这,首先采取的便是加锁机制了。如果仅仅为单机服务,使用java自带的锁就可以解决问题了,但是在大型项目中,服务往往以集群方式部署,集群中,加锁使用的往往为分布式锁,下面展示两种加锁代码。
单机解决,
synchronized
//锁住代码块,单机情况下串行方式执行下面代码块
synchronized (this){
//获取库存数目
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
//库存数目大于零则减库存
if(stock > 0){
int finalStock = stock - 1;
//更新库存
redisTemplate.opsForValue().set("stock",Integer.toString(finalStock));
}
}
加分布式锁
String lockKey = "lockKey";
//使用redis来实现分布式锁
Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "lack");
//如果加锁失败,就返回业务繁忙
if (!flag) {
return false;
}
//获取库存数目
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
//库存数目大于零则减库存
if (stock > 0) {
int finalStock = stock - 1;
//更新库存
redisTemplate.opsForValue().set("stock", Integer.toString(finalStock));
}
//业务执行完成,删除锁
redisTemplate.delete(lockKey);
2. 案例分析
在上面分布式锁加上后,一般情况下是没什么问题了,但是在几种特殊情况下,会有其他事件产生,下面对几种情况进行分析:
2.1 服务加锁后由于其他原因未解锁
针对这种情况,应当将解锁操作放置finally
中
finally {
//业务执行完成,删除锁
redisTemplate.delete(lockKey);
}
但是如果在进一步思考,如果由于服务宕机了锁没解开呢?对于这种情况,应当给锁设置过期时间,一定时间后自动解锁。
String lockKey = "lockKey";
//使用redis来实现分布式锁
Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "lack");
//10秒后自动过期
redisTemplate.expire(lockKey,10, TimeUnit.SECONDS);
2.2 服务加锁后,另一线程进入将当前线程锁解掉
上图中,展示一种情况,当A线程执行代码时间超过了运行时间,导致后面释放的实际上是线程B加的锁,那么,针对这种情况,我们可以在加锁时为锁设置相应的标识,例如uuid
String lockKey = "lockKey";
String uuid= UUID.randomUUID().toString();
//使用redis来实现分布式锁
Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid,10,TimeUnit.SECONDS);
.
.
.
//解锁,是上面加的锁才解锁
if(uuid.equals(redisTemplate.opsForValue().get(lockKey))){
redisTemplate.delete(lockKey);
}
2.3 锁过期,代码逻辑还未处理完
在上面设置的过期时间,若锁因为过期删除,而代码还在运行,则又会导致超卖问题发生,对于这点,很容易想到Redisson
的看门狗机制
,在这种情况,可以引入redission来解决。
到此,本案例分析到此就结束了,欢迎大家指出错误。
更多推荐
所有评论(0)