拼团削峰填谷与异步链路方案

拼团削峰填谷与异步链路方案

一、本地DelayQueue队列实现
1.1 延迟队列核心实现
@Component
public class DelayQueueManager {
    // 本地延迟队列,订单将在指定延迟时间后被处理
    private final DelayQueue<OrderDelayTask> delayQueue = new DelayQueue<>();
    private final ExecutorService consumerExecutor;
    private volatile boolean isRunning = true;

    @Autowired
    private OrderProcessor orderProcessor;

    public DelayQueueManager() {
        // 初始化消费者线程池
        this.consumerExecutor = new ThreadPoolExecutor(
            2, // 核心线程数
            4, // 最大线程数
            60, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100),
            new ThreadFactoryBuilder().setNameFormat("delay-queue-consumer-%d").build(),
            new ThreadPoolExecutor.AbortPolicy()
        );

        // 启动消费者
        startConsumers();
    }

    // 启动多个消费者线程
    private void startConsumers() {
        for (int i = 0; i < 2; i++) {
            consumerExecutor.submit(this::consumeTasks);
        }
    }

    // 消费延迟任务
    private void consumeTasks() {
        while (isRunning) {
            try {
                // 阻塞获取到期任务
                OrderDelayTask task = delayQueue.take();
                // 处理订单
                orderProcessor.processOrder(task.getOrderDTO());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.warn("延迟队列消费者被中断");
                break;
            } catch (Exception e) {
                log.error("处理延迟任务异常", e);
            }
        }
    }

    // 添加任务到延迟队列
    public boolean addTask(OrderCreateDTO orderDTO, long delayTime, TimeUnit unit) {
        try {
            long expireTime = System.currentTimeMillis() + unit.toMillis(delayTime);
            OrderDelayTask task = new OrderDelayTask(orderDTO, expireTime);
            return delayQueue.offer(task);
        } catch (Exception e) {
            log.error("添加任务到延迟队列失败", e);
            return false;
        }
    }

    // 延迟任务定义
    public static class OrderDelayTask implements Delayed {
        private final OrderCreateDTO orderDTO;
        private final long expireTime;

        public OrderDelayTask(OrderCreateDTO orderDTO, long expireTime) {
            this.orderDTO = orderDTO;
            this.expireTime = expireTime;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            return Long.compare(this.expireTime, ((OrderDelayTask) o).expireTime);
        }

        public OrderCreateDTO getOrderDTO() {
            return orderDTO;
        }
    }

    // 销毁方法
    @PreDestroy
    public void destroy() {
        isRunning = false;
        consumerExecutor.shutdown();
        try {
            if (!consumerExecutor.awaitTermination(1, TimeUnit.SECONDS)) {
                consumerExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            consumerExecutor.shutdownNow();
        }
    }
}
1.2 队列服务实现调整
@Service
public class OrderQueueServiceImpl implements OrderQueueService {
    @Autowired
    private DelayQueueManager delayQueueManager;

    @Autowired
    private KafkaTemplate<String, OrderCreateDTO> kafkaTemplate;

    @Value("${queue.mode:local}") // 可配置切换本地/分布式队列
    private String queueMode;

    @Value("${queue.local.delay-time:0}") // 本地队列延迟时间,单位秒
    private long localDelayTime;

    @Override
    public boolean sendToQueue(OrderCreateDTO orderDTO) {
        try {
            if ("local".equals(queueMode)) {
                // 添加到本地延迟队列,延迟0秒即立即处理(可配置)
                return delayQueueManager.addTask(orderDTO, localDelayTime, TimeUnit.SECONDS);
            } else {
                // Kafka分布式队列(保持不变)
                ListenableFuture<SendResult<String, OrderCreateDTO>> future =
                    kafkaTemplate.send("group-buying-order-topic", orderDTO.getOrderId(), orderDTO);
                future.addCallback(
                    result -> log.info("订单发送Kafka成功, orderId:{}", orderDTO.getOrderId()),
                    ex -> log.error("订单发送Kafka失败, orderId:{}", orderDTO.getOrderId(), ex)
                );
                return true;
            }
        } catch (Exception e) {
            log.error("订单加入队列失败", e);
            return false;
        }
    }
}
二、本地消息表+定时任务重试实现(
2.1 消息表设计
CREATE TABLE `local_message` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `message_id` varchar(64) NOT NULL COMMENT '消息唯一ID',
  `business_type` varchar(32) NOT NULL COMMENT '业务类型',
  `business_id` varchar(64) NOT NULL COMMENT '业务ID',
  `message_content` text NOT NULL COMMENT '消息内容(JSON)',
  `status` tinyint(4) NOT NULL COMMENT '状态:0-待处理,1-处理中,2-处理成功,3-处理失败',
  `retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '重试次数',
  `max_retry_count` int(11) NOT NULL DEFAULT '3' COMMENT '最大重试次数',
  `next_retry_time` datetime NOT NULL COMMENT '下次重试时间',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_message_id` (`message_id`),
  KEY `idx_status_next_retry_time` (`status`,`next_retry_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='本地消息表';
2.2 消息表实体与Mapper
@Data
@TableName("local_message")
public class LocalMessage {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String messageId;
    private String businessType;
    private String businessId;
    private String messageContent;
    private Integer status;
    private Integer retryCount;
    private Integer maxRetryCount;
    private Date nextRetryTime;
    private Date createTime;
    private Date updateTime;
}
2.3 消息服务实现
@Service
public class LocalMessageServiceImpl implements LocalMessageService {
    @Autowired
    private LocalMessageMapper localMessageMapper;

    @Autowired
    private ObjectMapper objectMapper;

    // 保存消息
    @Override
    @Transactional
    public void saveMessage(String businessType, String businessId, Object content) {
        try {
            String messageId = IdGenerator.generate();
            String contentJson = objectMapper.writeValueAsString(content);

            LocalMessage message = new LocalMessage();
            message.setMessageId(messageId);
            message.setBusinessType(businessType);
            message.setBusinessId(businessId);
            message.setMessageContent(contentJson);
            message.setStatus(0); // 待处理
            message.setRetryCount(0);
            message.setMaxRetryCount(3);
            message.setNextRetryTime(new Date()); // 立即处理
            message.setCreateTime(new Date());
            message.setUpdateTime(new Date());

            localMessageMapper.insert(message);
        } catch (Exception e) {
            log.error("保存本地消息失败", e);
            throw new BusinessException(ReturnCodeEnum.SYSTEM_ERROR, "保存消息失败");
        }
    }

    // 更新消息状态
    @Override
    @Transactional
    public boolean updateMessageStatus(String messageId, int status) {
        LocalMessage message = new LocalMessage();
        message.setMessageId(messageId);
        message.setStatus(status);
        message.setUpdateTime(new Date());
        return localMessageMapper.updateByMessageId(message) > 0;
    }

    // 处理失败,更新重试信息
    @Override
    @Transactional
    public boolean handleMessageFailure(String messageId) {
        LocalMessage message = localMessageMapper.selectByMessageId(messageId);
        if (message == null) {
            log.warn("消息不存在, messageId:{}", messageId);
            return false;
        }

        int newRetryCount = message.getRetryCount() + 1;
        if (newRetryCount >= message.getMaxRetryCount()) {
            // 达到最大重试次数,标记为最终失败
            message.setStatus(3);
            message.setRetryCount(newRetryCount);
            message.setUpdateTime(new Date());
            localMessageMapper.updateByMessageId(message);
            return false;
        }

        // 计算下次重试时间(指数退避策略)
        long delayMillis = (long) (Math.pow(2, newRetryCount) * 1000); // 2^retryCount秒
        Date nextRetryTime = new Date(System.currentTimeMillis() + delayMillis);

        message.setRetryCount(newRetryCount);
        message.setNextRetryTime(nextRetryTime);
        message.setStatus(0); // 重置为待处理
        message.setUpdateTime(new Date());
        localMessageMapper.updateByMessageId(message);
        return true;
    }

    // 获取待重试消息
    @Override
    public List<LocalMessage> getRetryMessages(int limit) {
        return localMessageMapper.selectRetryMessages(0, new Date(), limit);
    }
}
2.4 定时任务重试实现
@Component
public class MessageRetryTask {
    @Autowired
    private LocalMessageService localMessageService;
    @Autowired
    private OrderProcessor orderProcessor;
    @Autowired
    private ObjectMapper objectMapper;

    // 每30秒执行一次
    @Scheduled(cron = "0/30 * * * * ?")
    public void retryFailedMessages() {
        log.info("开始执行消息重试任务");
        try {
            // 每次最多处理100条
            List<LocalMessage> messages = localMessageService.getRetryMessages(100);
            if (CollectionUtils.isEmpty(messages)) {
                return;
            }

            for (LocalMessage message : messages) {
                // 乐观锁更新状态为处理中
                boolean locked = localMessageService.lockMessageForProcessing(message.getMessageId());
                if (!locked) {
                    continue;
                }

                try {
                    // 反序列化消息内容
                    OrderCreateDTO orderDTO = objectMapper.readValue(
                        message.getMessageContent(), OrderCreateDTO.class);

                    // 处理订单
                    orderProcessor.processOrder(orderDTO);

                    // 标记成功
                    localMessageService.updateMessageStatus(message.getMessageId(), 2);
                    log.info("消息重试成功, messageId:{}", message.getMessageId());
                } catch (Exception e) {
                    log.error("消息重试处理异常, messageId:{}", message.getMessageId(), e);
                    // 处理失败,更新重试信息
                    localMessageService.handleMessageFailure(message.getMessageId());
                }
            }
        } catch (Exception e) {
            log.error("消息重试任务执行异常", e);
        }
    }
}
2.5 订单处理器调整
@Component
public class OrderProcessor {
    @Autowired
    private GroupActivityService groupActivityService;
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private OrderService orderService;
    @Autowired
    private LocalMessageService localMessageService;

    // 处理订单
    public void processOrder(OrderCreateDTO orderDTO) {
        String orderId = orderDTO.getOrderId();
        String messageId = IdGenerator.generate();

        try {
            // 1. 保存本地消息(确保消息可靠性)
            localMessageService.saveMessage("GROUP_BUYING_ORDER", orderId, orderDTO);

            // 2. 幂等性检查
            if (orderService.isOrderExists(orderId)) {
                log.warn("订单已存在, orderId:{}", orderId);
                localMessageService.updateMessageStatus(messageId, 2);
                return;
            }

            // 3. 校验活动状态
            ActivityStatusDTO activityStatus = groupActivityService.checkActivityStatus(
                orderDTO.getActivityId(), orderDTO.getProductId());
            if (!activityStatus.isValid()) {
                orderService.updateOrderStatus(orderId, OrderStatusEnum.FAILED, "活动已结束或未开始");
                localMessageService.updateMessageStatus(messageId, 2);
                return;
            }

            // 4. 扣减库存
            boolean inventoryDeducted = inventoryService.deductInventory(
                orderDTO.getProductId(), orderDTO.getQuantity(), orderId);
            if (!inventoryDeducted) {
                orderService.updateOrderStatus(orderId, OrderStatusEnum.FAILED, "库存不足");
                localMessageService.updateMessageStatus(messageId, 2);
                return;
            }

            // 5. 创建订单
            OrderVO orderVO = orderService.createOrder(orderDTO);

            // 6. 更新拼团状态
            groupActivityService.updateGroupStatus(orderDTO.getGroupId(), orderVO);

            // 7. 标记消息处理成功
            localMessageService.updateMessageStatus(messageId, 2);

            // 8. 发送订单成功通知
            notificationService.sendOrderSuccessNotification(orderVO);

        } catch (Exception e) {
            log.error("订单处理异常, orderId:{}", orderId, e);
            // 标记消息处理失败
            localMessageService.handleMessageFailure(messageId);
            // 库存回滚
            inventoryService.revertInventory(orderDTO.getProductId(), orderDTO.getQuantity(), orderId);
        }
    }
}
三、关键调整说明
  1. 本地DelayQueue优势

    • JDK原生实现,无需额外依赖
    • 支持延迟处理,可用于实现排队机制
    • 内存级队列,低延迟高吞吐
    • 支持多消费者并行处理
  2. 本地消息表+定时任务优势

    • 完全基于数据库,部署简单
    • 消息持久化,系统重启后可恢复
    • 重试策略灵活,支持指数退避
    • 便于监控和人工干预失败消息
    • 避免引入Kafka等中间件的复杂性
  3. 重试机制说明

    • 采用指数退避策略:重试间隔 = 2^重试次数 秒
    • 最大重试次数默认3次,可配置
    • 每次重试前更新消息状态为"处理中",避免并发处理
    • 达到最大重试次数后标记为失败,支持人工介入
  4. 事务保障

    • 消息保存与业务操作在同一事务中
    • 基于本地数据库事务,确保消息可靠投递
    • 处理结果实时更新到消息表

通过以上调整,系统在保持削峰填谷能力的同时,简化了技术栈(去除Kafka依赖),提高了消息处理的可靠性,更适合中小规模的拼团业务场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@淡 定

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

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

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

打赏作者

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

抵扣说明:

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

余额充值