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


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

    引言

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

    方案设计

    基本思路

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

    具体实现

    Lua 脚本

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

    Java 代码

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

    1. @Service
    2. @Slf4j
    3. public class VoucherOrderServiceImpl extends ServiceImpl implements IVoucherOrderService {
    4. @Resource
    5. private ISeckillVoucherService seckillVoucherService;
    6. @Resource
    7. private RedisIdWorker redisIdWorker;
    8. @Resource
    9. private StringRedisTemplate stringRedisTemplate;
    10. @Resource
    11. private RedissonClient redissonClient;
    12. private static final DefaultRedisScript SECKILL_SCRIPT;
    13. static {
    14. SECKILL_SCRIPT = new DefaultRedisScript<>();
    15. SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
    16. SECKILL_SCRIPT.setResultType(Long.class);
    17. }
    18. private BlockingQueue orderTasks = new ArrayBlockingQueue<>(1024 * 1024);
    19. private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
    20. @PostConstruct
    21. private void init() {
    22. SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    23. }
    24. private class VoucherOrderHandler implements Runnable {
    25. @Override
    26. public void run() {
    27. while (true) {
    28. try {
    29. VoucherOrder voucherOrder = orderTasks.take();
    30. handleVoucherOrder(voucherOrder);
    31. } catch (Exception e) {
    32. log.error("创建订单失败", e);
    33. }
    34. }
    35. }
    36. }
    37. private void handleVoucherOrder(VoucherOrder voucherOrder) {
    38. Long userId = voucherOrder.getUserId();
    39. RLock lock = redissonClient.getLock("lock:order:" + userId);
    40. boolean isLock = lock.tryLock();
    41. if (!isLock) {
    42. log.error("不允许重复下单");
    43. return;
    44. }
    45. try {
    46. proxy.crateVoucherOrder(voucherOrder);
    47. } finally {
    48. lock.unlock();
    49. }
    50. }
    51. private IVoucherOrderService proxy;
    52. @Override
    53. public Result seckillVoucher(Long voucherId) {
    54. Long userId = UserHolder.getUser().getId();
    55. Long result = stringRedisTemplate.execute(
    56. SECKILL_SCRIPT,
    57. Collections.emptyList(),
    58. voucherId.toString(), userId.toString()
    59. );
    60. int r = result.intValue();
    61. if (r != 0) {
    62. return Result.fail(r == 1 ? "库存不足" : "不能重复下单");
    63. }
    64. VoucherOrder voucherOrder = new VoucherOrder();
    65. long orderId = redisIdWorker.nextId("order");
    66. voucherOrder.setId(orderId);
    67. voucherOrder.setUserId(userId);
    68. voucherOrder.setVoucherId(voucherId);
    69. orderTasks.add(voucherOrder);
    70. proxy = (IVoucherOrderService) AopContext.currentProxy();
    71. return Result.ok(orderId);
    72. }
    73. @Override
    74. @Transactional
    75. public void crateVoucherOrder(VoucherOrder voucherOrder) {
    76. Long userId = voucherOrder.getUserId();
    77. int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
    78. if (count > 0) {
    79. log.error("不允许重复下单!");
    80. return;
    81. }
    82. boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id",
    83. voucherOrder.getVoucherId())
    84. .gt("stock", 0).update();
    85. if (!success) {
    86. log.error("库存不足!");
    87. return;
    88. }
    89. save(voucherOrder);
    90. }
    91. }

    代码详解

    1. 初始化阻塞队列和线程池

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

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

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

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

    结论

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

    可能出现的问题

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

  • 相关阅读:
    git常用命令积累[持续更新]
    动态链接库--dll使用示例
    基于SpringBoot+Druid实现多数据源:注解+编程式
    InetAddress.getLocalHost() 执行很慢?
    基于ssm的高校阅读分享推荐系统
    数据通信网络之IPv6以太网多层交换
    Win10 笔记本本地摄像头提供 Rtsp 视频流服务
    cv面试百问day2
    java毕业设计基于的电商平台的设计与实现Mybatis+系统+数据库+调试部署
    300行代码实现Minecraft(我的世界)大地图生成
  • 原文地址:https://blog.csdn.net/Takumilove/article/details/140041696