在上一篇博客中,我们介绍了如何利用 Redis 和 Lua 脚本来高效处理秒杀活动中的高并发请求,保证用户体验。本文将进一步优化秒杀系统,通过引入阻塞队列实现异步下单,从而提高系统的整体性能和稳定性。
秒杀活动往往伴随着极高的并发请求,对系统的性能和稳定性提出了巨大挑战。同步处理订单请求可能导致数据库压力过大,影响系统响应时间。为了缓解这一问题,我们可以采用异步下单的方式,将订单请求先放入阻塞队列,由后台线程逐一处理,从而降低数据库的瞬时压力。
Lua 脚本的逻辑保持不变,继续用于判断秒杀资格和扣减库存。
在 Java 代码中,我们通过阻塞队列实现异步下单,并利用 Redisson 分布式锁来确保订单操作的线程安全。
- @Service
- @Slf4j
- public class VoucherOrderServiceImpl extends ServiceImpl
implements IVoucherOrderService { -
- @Resource
- private ISeckillVoucherService seckillVoucherService;
-
- @Resource
- private RedisIdWorker redisIdWorker;
-
- @Resource
- private StringRedisTemplate stringRedisTemplate;
-
- @Resource
- private RedissonClient redissonClient;
-
- private static final DefaultRedisScript
SECKILL_SCRIPT; -
- static {
- SECKILL_SCRIPT = new DefaultRedisScript<>();
- SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
- SECKILL_SCRIPT.setResultType(Long.class);
- }
-
- private BlockingQueue
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);
- }
- }
初始化阻塞队列和线程池:
BlockingQueue 和 ExecutorService 实现一个单线程的订单处理机制,在服务初始化时启动订单处理线程。秒杀请求处理:
订单处理线程:
事务处理:
通过引入阻塞队列实现异步下单,我们有效地减少了数据库的瞬时压力,提高了系统的整体性能和稳定性。这种方法不仅适用于秒杀活动,还可以推广到其他高并发场景,如抢购、促销活动等。希望本文对您理解和实现高并发系统有所帮助。
我在一次批量用一千个线程去抢优惠卷的时候发现,优惠卷没有抢完,初步判断是阻塞队列的大小过小,内存的限制问题。