我们先来回顾一下下单流程
当用户发起请求,此时会先请求Nginx,Nginx反向代理到Tomcat,而Tomcat中的程序,会进行串行操作,分为如下几个步骤
在这六个步骤中,有很多操作都是要去操作数据库的,而且还是一个线程串行执行,这样就会导致我们的程序执行很慢,所以我们需要异步程序执行,那么如何加速呢?
优化方案:
我们将耗时较短的逻辑判断放到Redis中,例如:库存是否充足,是否一人一单这样的操作,只要满足这两条操作,那我们是一定可以下单成功的,不用等数据真的写进数据库,我们直接告诉用户下单成功就好了。然后后台再开一个线程,后台线程再去慢慢执行队列里的消息,这样我们就能很快的完成下单业务。
步骤一:
修改保存优惠券相关代码@Override
@Transactional
public void addSeckillVoucher(Voucher voucher) {
// 保存优惠券
save(voucher);
// 保存秒杀信息
SeckillVoucher seckillVoucher = new SeckillVoucher();
seckillVoucher.setVoucherId(voucher.getId());
seckillVoucher.setStock(voucher.getStock());
seckillVoucher.setBeginTime(voucher.getBeginTime());
seckillVoucher.setEndTime(voucher.getEndTime());
seckillVoucherService.save(seckillVoucher);
// 保存秒杀优惠券信息到Reids,Key名中包含优惠券ID,Value为优惠券的剩余数量
stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString());
}
使用PostMan发送请求,添加优惠券
请求路径:
http://localhost:8080/api/voucher/seckill
请求方式:POST
{
"shopId":1,
"title":"9999元代金券",
"subTitle":"365*24小时可用",
"rules":"全场通用\\\\nApex猎杀无需预约",
"payValue":1000,
"actualValue":999900,
"type":1,
"stock":100,
"beginTime":"2022-01-01T00:00:00",
"endTime":"2022-12-31T23:59:59"
}
添加成功后,数据库中和Redis中都能看到优惠券信息
步骤二:
编写Lua脚本lua的字符串拼接使用..
,字符串转数字是tonumber()
-- 订单id
local voucherId = ARGV[1]
-- 用户id
local userId = ARGV[2]
-- 优惠券key
local stockKey = 'seckill:stock:' .. voucherId
-- 订单key
local orderKey = 'seckill:order:' .. voucherId
-- 判断库存是否充足
if (tonumber(redis.call('get', stockKey)) <= 0) then
return 1
end
-- 判断用户是否下单
if (redis.call('sismember', orderKey, userId) == 1) then
return 2
end
-- 扣减库存
redis.call('incrby', stockKey, -1)
-- 将userId存入当前优惠券的set集合
redis.call('sadd', orderKey, userId)
return 0
修改业务逻辑
@Override
public Result seckillVoucher(Long voucherId) {
//1. 执行lua脚本
Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,
Collections.emptyList(), voucherId.toString(),
UserHolder.getUser().getId().toString());
//2. 判断返回值,并返回错误信息
if (result.intValue() != 0) {
return Result.fail(result.intValue() == 1 ? "库存不足" : "不能重复下单");
}
long orderId = redisIdWorker.nextId("order");
//TODO 保存阻塞队列
//3. 返回订单id
return Result.ok(orderId);
}
现在我们使用PostMan发送请求,redis中的数据会变动,而且不能重复下单,但是数据库中的数据并没有变化