基于阻塞队列实现异步下单,提升秒杀系统性能

目录

一、引入阻塞队列的设计思路

二、基于阻塞队列的异步下单代码实现

三、代码详解

四、结论与优化建议


在秒杀系统中,用户的并发请求数量往往远超系统的承受能力。如果直接在用户请求中同步执行订单创建操作,会导致数据库的瞬时压力过大,从而影响系统的整体性能和用户体验。为了解决这个问题,本文将介绍如何通过引入阻塞队列来实现异步下单,并进一步优化系统的并发性能。


一、引入阻塞队列的设计思路

在高并发场景下,秒杀请求通常分为两个阶段:

  1. 秒杀资格判断:

    • 用户发起秒杀请求时,通过 Redis 和 Lua 脚本对用户进行秒杀资格的判断。
    • 在该步骤中,主要操作包括库存判断、用户重复判断和库存扣减等。
  2. 订单创建:

    • 用户通过秒杀资格判断后,需要生成订单并将其持久化到数据库中。
    • 如果直接在请求线程中进行订单创建操作,数据库在短时间内可能会因为过多的请求而出现阻塞或性能下降。

为了解决这一问题,我们引入了阻塞队列来进行异步下单。具体流程如下:

  1. 用户请求通过 Lua 脚本进行秒杀资格判断,并扣减 Redis 中的库存。
  2. 将订单信息(如用户 ID、优惠券 ID)放入阻塞队列中。
  3. 后台线程从阻塞队列中逐一取出订单信息,并创建订单。
  4. 通过引入阻塞队列和异步处理,避免了数据库瞬时写入压力过大的问题,从而提升系统的整体性能。

二、基于阻塞队列的异步下单代码实现

下面是完整的 Java 实现代码,通过阻塞队列和后台线程实现异步订单创建。

@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    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 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.error("不允许重复下单");
            return;
        }
        try {
            proxy.crateVoucherOrder(voucherOrder);
        } finally {
            lock.unlock();
        }
    }

    private IVoucherOrderService proxy;

    @Override
    public Result seckillVoucher(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(), userId.toString()
        );
        int r = result.intValue();
        if (r != 0) {
            return Result.fail(r == 1 ? "库存不足" : "不能重复下单");
        }
        VoucherOrder voucherOrder = new VoucherOrder();
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        voucherOrder.setUserId(userId);
        voucherOrder.setVoucherId(voucherId);
        orderTasks.add(voucherOrder);
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        return Result.ok(orderId);
    }

    @Override
    @Transactional
    public void crateVoucherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
        int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
        if (count > 0) {
            log.error("不允许重复下单!");
            return;
        }
        boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id",
                        voucherOrder.getVoucherId())
                .gt("stock", 0).update();
        if (!success) {
            log.error("库存不足!");
            return;
        }
        save(voucherOrder);
    }
}

三、代码详解
  1. Lua 脚本判断秒杀资格:

    • 使用 Redis 和 Lua 脚本完成秒杀资格判断和库存扣减,避免了数据库在高并发场景下的竞争。
  2. 阻塞队列存储订单信息:

    • 通过 BlockingQueue<VoucherOrder> orderTasks 来存放用户秒杀成功的订单信息。阻塞队列能够很好地缓冲并发请求,防止订单数据丢失。
  3. 后台线程异步处理订单:

    • 通过后台线程 VoucherOrderHandler 来从阻塞队列中逐一取出订单信息,并进行订单创建。
    • 使用 Redisson 分布式锁来保证用户订单操作的线程安全性。
  4. 事务管理和数据一致性:

    • 在订单创建方法中使用了 Spring 的 @Transactional 注解,确保订单创建和库存扣减的操作具有原子性和一致性。

四、结论与优化建议

通过引入阻塞队列和异步下单处理,我们有效地减少了数据库的瞬时写入压力,提高了系统的整体性能和稳定性。该方案不仅适用于秒杀活动,还可以推广到其他高并发场景(如抢购、促销活动等)。

可能的优化点:

  1. 增加阻塞队列容量:

    • 在高并发测试中,如果阻塞队列的容量过小(如 1024),可能会出现队列溢出的问题。建议在内存允许的情况下,适当增大队列容量。
  2. 引入消息队列:

    • 如果并发量特别高,可以考虑将订单信息写入 Kafka 或 RabbitMQ 等消息队列,以进一步提升系统的扩展性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Takumilovexu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值