场景说明

设计秒杀活动时,因需要保证在高并发场景下库存不会超卖,或 抢券不会超量抢券的情况,可使用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");