原思路:
因为时串行执行且多次调用数据库,所以效率较慢,所以引入队列概念
优化思路:
优化流程(1:库存不足,2:用户已经下单,无法重复下单确保一人一单,0:有下单资格)
1.修改添加优惠卷业务,在添加优惠卷到数据库的同时添加到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);
//保存优惠卷信息到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. 判断库存是否充足 get stockKey
if (tonumber(redis.call('get',stockKey)) <= 0 ) then
-- 3.2. 库存不足,返回1
return 1
end
-- 3.2. 判断用户是否下单 SISMEMBER orderKey userId
if (redis.call('SISMEMBER',orderKey,userId) == 1) then
-- 3.3. 存在,说明是重复下单,返回2
return 2
end
-- 3.4. 扣库存 incrby stockKey -1
redis.call('incrby',stockKey, -1)
-- 3.5. 下单(保存用户) sadd orderKey userId
redis.call('sadd',orderKey,userId)
return 0
3.重写业务类
private IVoucherOrderService proxy;
@Override
public Result seckillVoucher(Long voucherId) {
//获取用户
Long userId = UserHolder.getUser().getId();
//执行lua脚本
Long result = stringRedisTemplate.execute(
SECKILL_SCRIPT,//脚本文件
Collections.emptyList(),//key的集合
voucherId.toString(), userId.toString() //两个ARGV
);
//将返回值转换为int
int i = result.intValue();
//判断结果是否为零
if (i != 0 ){
//不为零,无法购买 ---1为库存不足,2为用户已经下单
return Result.fail(i == 1 ? "库存不足" : "不能重复下单");
}
//可以购买,将下单信息保存到阻塞队列
//生成订单id
long orderId = redisIdWorker.nextId("order");
// todo 保存到阻塞队列
VoucherOrder voucherOrder = new VoucherOrder();
//代金卷id
voucherOrder.setVoucherId(voucherId);
//id
voucherOrder.setId(orderId);
//用户id
voucherOrder.setUserId(userId);
//放入阻塞队列
orderTasks.add(voucherOrder);
//获取代理对象
proxy = (IVoucherOrderService) AopContext.currentProxy();
//返回订单id
return Result.ok(orderId);
}
4.完成异步下单
//创建阻塞队列
private BlockingQueue<VoucherOrder> orderTasks =new ArrayBlockingQueue<>(1024*1024);
//创建线程池
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
@PostConstruct
private void init(){
SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}
private class VoucherOrderHandler implements Runnable{
@Override
public void run() {
while (true){
try {
//1. 获取队列中的订单信息
VoucherOrder voucherOrder = orderTasks.take();
//2.创建订单
handleVoucherOrder(voucherOrder);
} catch (InterruptedException e) {
log.error("处理订单异常",e);
}
}
}
}
/**
* 创建订单业务(在异步处理阻塞队列时)
* @param voucherOrder
*/
private void handleVoucherOrder(VoucherOrder voucherOrder) {
//1.获取用户
Long userId = voucherOrder.getUserId();
//2.获取锁对象
RLock lock = redissonClient.getLock("lock:order:" + userId);
//3.获取锁
boolean isLock = lock.tryLock();
//判断是否获取锁成功
if (!isLock){
//获取锁失败,记录日志
log.error("不允许重复下单");
return;
}
try {
proxy.createVoucherOrder(voucherOrder);
} finally {
lock.unlock();
}
}
@Transactional
public void createVoucherOrder(VoucherOrder voucherOrder) {
//从拦截器中获取用户id
Long userId = voucherOrder.getUserId();
Long voucherId = voucherOrder.getVoucherId();
/**
* 进行判断,一人一单
*/
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
if (count > 0) {
//用户已经购买过商品
log.error("用户已经购买过商品");
return;
}
//扣减库存
boolean update = iSeckillVoucherService.update().setSql("stock = stock - 1")
.eq("voucher_id", voucherId)
.gt("stock", 0)
.update();
//判断库存扣减是否成功
if (!update) {
//库存扣减失败,返回信息
log.error("库存不足");
return ;
}
System.out.println(voucherOrder.toString());
//添加到数据库
boolean save = save(voucherOrder);
log.error("---------------------------");
if (!save) {
//添加失败
log.error("添加失败");
return ;
}
}