基于Redis完成秒杀资格判断
改进秒杀业务,需求:
- 新增秒杀优惠券同时,将优惠券信息保存到Redis中
- 基于Lua脚本,判断秒杀库存、一人一单,决定用户是否抢购成功
- 如果抢购成功,将优惠券id和用户id封装后存入阻塞队列
- 开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能 ```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
<a name="zNuZS"></a>## 基于阻塞队列实现异步下单```luaprivate static final DefaultRedisScript<Long> SECKILL_SCRIPT;static {SECKILL_SCRIPT = new DefaultRedisScript<>();SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);}private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();@PostConstructprivate void init(){SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}private class VoucherOrderHandler implements Runnable{@Overridepublic void run() {while(true){try {VoucherOrder voucherOrder = orderTasks.take();createVoucherOrder(voucherOrder);}catch(Exception e){log.error("处理订单异常", e);}}}}@Overridepublic Result seckillVoucher(Long voucherId) {Long userId = UserHolder.getUser().getId();long orderId = redisIdWorker.nextId("order");// 1.执行lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString(), String.valueOf(orderId));int r = result.intValue();// 2.判断结果是否为0if (r != 0) {// 2.1.不为0 ,代表没有购买资格return Result.fail(r == 1 ? "库存不足" : "不能重复下单");}//2.2 为0,有购买资格,把下单信息保存到阻塞队列VoucherOrder voucherOrder = new VoucherOrder();//2.3 订单idvoucherOrder.setId(orderId);//2.4 用户idvoucherOrder.setUserId(userId);//2.5代金券idvoucherOrder.setVoucherId(voucherId);//2.6放入阻塞队列orderTasks.add(voucherOrder);return Result.ok(orderId);}private void createVoucherOrder(VoucherOrder voucherOrder) {Long userId = voucherOrder.getUserId();Long voucherId = voucherOrder.getVoucherId();// 创建锁对象RLock redisLock = redissonClient.getLock("lock:order:" + userId);// 尝试获取锁boolean isLock = redisLock.tryLock();// 判断if (!isLock) {// 获取锁失败,直接返回失败或者重试log.error("不允许重复下单!");return;}try {// 5.1.查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();// 5.2.判断是否存在if (count > 0) {// 用户已经购买过了log.error("不允许重复下单!");return;}// 6.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1") // set stock = stock - 1.eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0.update();if (!success) {// 扣减失败log.error("库存不足!");return;}// 7.创建订单save(voucherOrder);} finally {// 释放锁redisLock.unlock();}}

