基于上一篇博客,用阻塞队列实现异步下单

在上一篇博客中,我们介绍了如何利用 Redis 和 Lua 脚本来高效处理秒杀活动中的高并发请求,保证用户体验。本文将进一步优化秒杀系统,通过引入阻塞队列实现异步下单,从而提高系统的整体性能和稳定性。

引言

秒杀活动往往伴随着极高的并发请求,对系统的性能和稳定性提出了巨大挑战。同步处理订单请求可能导致数据库压力过大,影响系统响应时间。为了缓解这一问题,我们可以采用异步下单的方式,将订单请求先放入阻塞队列,由后台线程逐一处理,从而降低数据库的瞬时压力。

方案设计

基本思路

  1. 用户发起秒杀请求,先通过 Redis Lua 脚本进行资格判断。
  2. 通过 Lua 脚本判断用户是否有购买资格,并扣减库存。
  3. 将订单信息放入阻塞队列中,由后台线程异步处理订单创建和数据库操作。
  4. 返回订单 ID 给用户。

具体实现

Lua 脚本

Lua 脚本的逻辑保持不变,继续用于判断秒杀资格和扣减库存。

Java 代码

在 Java 代码中,我们通过阻塞队列实现异步下单,并利用 Redisson 分布式锁来确保订单操作的线程安全。

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. 初始化阻塞队列和线程池

    • 使用 BlockingQueueExecutorService 实现一个单线程的订单处理机制,在服务初始化时启动订单处理线程。
  2. 秒杀请求处理

    • 用户发起秒杀请求时,首先通过 Lua 脚本判断秒杀资格和扣减库存。
    • 如果有购买资格,将订单信息放入阻塞队列中。
  3. 订单处理线程

    • 订单处理线程从阻塞队列中取出订单,并在获取到用户锁后创建订单,防止同一用户重复下单。
  4. 事务处理

    • 在订单处理方法中使用事务管理,确保订单创建和库存扣减的原子性。

结论

通过引入阻塞队列实现异步下单,我们有效地减少了数据库的瞬时压力,提高了系统的整体性能和稳定性。这种方法不仅适用于秒杀活动,还可以推广到其他高并发场景,如抢购、促销活动等。希望本文对您理解和实现高并发系统有所帮助。

可能出现的问题

我在一次批量用一千个线程去抢优惠卷的时候发现,优惠卷没有抢完,初步判断是阻塞队列的大小过小,内存的限制问题。

相关推荐
知行合一。。。24 分钟前
RAG--03--Milvus基本用法
数据库·oracle·milvus
2601_9620715737 分钟前
【Java报错已解决】org.springframework.beans.factory.BeanCreationException
java·开发语言
TDengine (老段)1 小时前
TDengine 线程模型 — 网络、调度、执行
大数据·数据库·物联网·制造·时序数据库·tdengine·涛思数据
zww89491111 小时前
酒馆预约系统开发实战:从需求分析到上线全流程指南
java·eclipse
淡海水2 小时前
07-04-并发-ConcurrentBag-T-工作窃取WorkStealing算法
开发语言·算法·c#·bag·concurrent·workstealing
上学的小垃圾2 小时前
01-数据库系统概述
数据库
CS_Zero2 小时前
C语言sizeof是函数吗?
c语言·开发语言
二进制漫游记2 小时前
FastAPI项目集成 Qdrant向量数据库+阿里云Embedding完整实战(工具封装+业务调用)
数据库·python·阿里云·embedding
2501_928996223 小时前
GPT-4o换DeepSeek迁移成本多少?中科热备解析API聚合平台技术账本
前端·数据库·人工智能
莫得感情 o3 小时前
Redis 05 · 持久化:RDB 与 AOF 怎么保证数据不丢
redis·缓存