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

在上一篇博客中,我们介绍了如何利用 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. 事务处理

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

结论

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

可能出现的问题

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

相关推荐
lightqjx1 分钟前
【数据结构】顺序表(sequential list)
c语言·开发语言·数据结构·算法
巨人张10 分钟前
信息素养Python编程题
开发语言·python
东阳马生架构12 分钟前
订单初版—5.售后退货链路中的技术问题说明文档
java
小小寂寞的城18 分钟前
JAVA策略模式demo【设计模式系列】
java·设计模式·策略模式
阿猿收手吧!34 分钟前
【计算机网络】HTTP1.0 HTTP1.1 HTTP2.0 QUIC HTTP3 究极总结
开发语言·计算机网络
JAVA学习通35 分钟前
图书管理系统(完结版)
java·开发语言
abigalexy42 分钟前
深入Java锁机制
java
paishishaba42 分钟前
处理Web请求路径参数
java·开发语言·后端
七七七七0743 分钟前
C++类对象多态底层原理及扩展问题
开发语言·c++