Redis优化秒杀——异步秒杀

原本流程

修改后

如何在Redis中判断秒杀库存和校验一人一单

将信息存入Redis中

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 订单id
local orderKey = "seckill:order:" .. voucherId

--脚本业务
-- 3.1 判断库存是否充足
if (tonumber(redis.call("get", stockKey) )<= 0) then
    -- 库存不足
    return 1
end
-- 3.2判断用户是否下单
if(redis.call("sismember", orderKey, userId) == 1) then
    -- 已经下单
    return 2
end
-- 3.3 库存减一 incrby stockKey -1
redis.call("incrby", stockKey, -1)
-- 3.4 下单 sadd orderKey userId
redis.call("sadd", orderKey, userId)
return 0

完整实现流程

 private static final DefaultRedisScript<Long> SECKILL_SCRIPT;

    static{
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

    private IVoucherOrderService proxy;

    @Override
    public Result seckillVoucher(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        // 1.执行lua脚本
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(),
                userId.toString()
        );
        // 2.判断返回值是否为0
        int r = result.intValue();
        if (r != 0) {
            // 2.1 不为0 没有购买资格
            return Result.fail(r==1?"库存不足!":"已经抢过券了!");
        }
        // 2.2 为0 有购买资格 把下单信息保存到阻塞队列
        long orderId = redisIdWorker.nextId("order");
        VoucherOrder voucherorder = new VoucherOrder();
        voucherorder.setId(orderId);
        voucherorder.setVoucherId(voucherId);
        voucherorder.setUserId(userId);
        // 2.3放入阻塞队列
        orderTasks.add(voucherorder);
        // 3 获取代理对象
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        return Result.ok(orderId);
    }

线程中新增一个线程用来处理阻塞队列中的订单

@PostConstruct注解init方法在 类构造时开始执行

 // 阻塞队列
    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 {
                    // 获取队列中的订单信息
                    VoucherOrder voucherOrder = orderTasks.take();
                    // 创建订单
                    handleVoucherOrder(voucherOrder);
                }catch (Exception e){
                    log.error("处理订单异常", e);
                }
            }
        }
    }

    private void handleVoucherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
        RLock lock = redissonClient.getLock("lock:order:" + userId);
            // 获取锁
            boolean isLock = lock.tryLock();
            if(!isLock) {
                // 获取锁失败 返回错误信息或重试
                log.info("获取锁失败");
                return;
            }
            try {
                // 创建代理对象 解决事务问题
                proxy.createVoucherOrder(voucherOrder);
            }finally {
                lock.unlock();
            }
    }
@Transactional
    public  void createVoucherOrder(VoucherOrder voucherOrder) {
        // 一人一单
        Long userId = voucherOrder.getUserId();
            int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
            // 判断是否存在
            if (count > 0) {
                log.error("重复下单!");
            }
            // 5.扣减库存
            boolean success = seckillVoucherService.update()
                    .setSql("stock = stock - 1")
                    .eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0)
                    .update();
            if(!success) {
                log.error("库存不足!");
            }
            save(voucherOrder);


    }

秒杀的优化思路:

先利用Redis完成库存余量、一人一单判断,完成抢单业务
再将下单业务放入阻塞队列,利用独立线程异步下单

还存在的问题:

可能内存溢出 阻塞队列用的是JVM的内存

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值