秒杀
优化前
优化后
将库存量和一人一单的判断放在Redis中进行判断,如果满足下单条件异步完成下单操作,减少整体业务时间,和数据库交互次数。
判断库存是否充足,用户是否已经下过单,扣除库存,必须需要具有原子性,则使用Lua脚本完成
1.新增秒杀优惠券同时,保存在Redis中
private static final String SECKILL_STOCK_KEY = "seckill:stock:";
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void addSeckillVoucher(Voucher voucher) {
//新增秒杀卷 -- 保存数据库
//saveSeckillVoucher(voucher);
//将秒杀卷库存数量保存到Redis中
stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString());
}
2.利用Lua脚本,判断库存,一人一单,是否有抢购资格
--1删除列表
--1.1优惠券ID
local voucherId = ARGV[1]
--1.2 用户ID
local userId = ARGV[2]
--2.数据key
--2.1库存key
local stockKey = 'seckill:stock:' .. voucherId
--2.2订单key
local orderKey = 'seckill:order:' .. voucherId
--3.脚本业务
--3.1判断库存是否充足
--字符串不能跟数字进行比对,需要使用tonumber转为数字
if (tonumber(redis.call('get', stockKey)) <= 0) then
--3.2库存不足返回1
return 1
end
--3.3判断用户是否下单
if (redis.call('sismember', orderKey, userId) == 1) then
--3.4存在,说明用户重复下单,返回2
return 2
end
--3.5扣库存
redis.call('incrby', stockKey, -1)
--3.6下单保存用户
redis.call('sadd', orderKey, userId)
return 0
3.抢购成功加入阻塞队列
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
static {
SECKILL_SCRIPT = new DefaultRedisScript<>();
SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
SECKILL_SCRIPT.setResultType(Long.class);
}
@Autowired
private RedisIDGenerator redisIDGenerator;
public void seckillVoucher(Long voucherId, Long userId) {
//1.执行lua脚本
Long result = stringRedisTemplate.execute(
SECKILL_SCRIPT,
Collections.emptyList(),
voucherId.toStr