拼团削峰填谷与异步链路方案
一、本地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 消息表设计
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);
}
}
}
三、关键调整说明
-
本地DelayQueue优势:
- JDK原生实现,无需额外依赖
- 支持延迟处理,可用于实现排队机制
- 内存级队列,低延迟高吞吐
- 支持多消费者并行处理
-
本地消息表+定时任务优势:
- 完全基于数据库,部署简单
- 消息持久化,系统重启后可恢复
- 重试策略灵活,支持指数退避
- 便于监控和人工干预失败消息
- 避免引入Kafka等中间件的复杂性
-
重试机制说明:
- 采用指数退避策略:重试间隔 = 2^重试次数 秒
- 最大重试次数默认3次,可配置
- 每次重试前更新消息状态为"处理中",避免并发处理
- 达到最大重试次数后标记为失败,支持人工介入
-
事务保障:
- 消息保存与业务操作在同一事务中
- 基于本地数据库事务,确保消息可靠投递
- 处理结果实时更新到消息表
通过以上调整,系统在保持削峰填谷能力的同时,简化了技术栈(去除Kafka依赖),提高了消息处理的可靠性,更适合中小规模的拼团业务场景。