场景说明
设计秒杀活动时,因需要保证在高并发场景下库存不会超卖,或 抢券不会超量抢券的情况,可使用redis的decr功能 ,但是如果仅仅简单的使用decr,针对以下场景会有问题
- 剩余数量为1
- 线程1 decr 数量剩余0
- 线程2 decr 数量剩余-1 响应失败
- 线程1 因后续功能异常通过incr 归还数量
- 线程3 decr 数量剩余-1 响应失败
上述场景中,最后一个库存未使用便已不可用,在高并发场景下浪费的库存会较多,针对以上问题有以下两个方案
方案
方案1
当decr 结果小于0时,通过incr归还数量后返回失败
此方案的弊端是需要调用两次redis
方案2
通过lua脚本原子性处理
local count = redis.call('GET',KEYS[1])
if count then
if tonumber(count)>0 then
return redis.call('DECR',KEYS[1])
else
return -1
end
else
return nil
end
- 执行get命令,获取该key
- 如果value不存在则返回-2
- 如果value小于0则返回-1
- 如果value大于0,对通过decr -1 ,并返回减1后的值
脚本执行
- redis cli 执行脚本
redis-cli --eval script.lua key
- java执行脚本
@Resource
private StringRedisTemplate stringRedisTemplate;
DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/Test.lua")));
redisScript.setResultType(Integer.class);
List<String> keys = new ArrayList<>();
keys.add(key());
Integer count = stringRedisTemplate.execute(redisScript, keys, "100");