电商系统中订单超时关闭是一个经典场景。当用户下单后未在规定时间内完成支付,系统需要自动关闭订单并释放库存。传统实现方案通常采用数据库轮询,但随着系统规模扩大,这种方案暴露出诸多问题。
我经历过一个日订单量50万+的电商系统改造项目。最初使用Spring Task每分钟扫描订单表,高峰期数据库CPU直接飙到90%,DBA天天追着开发团队骂。后来我们调研了多种方案,最终选择RocketMQ延时消息,效果立竿见影。
数据库轮询方案通常是这样实现的:
java复制@Scheduled(fixedDelay = 60000)
public void scanExpiredOrders() {
List<Order> orders = orderMapper.selectExpiredOrders();
orders.forEach(this::closeOrder);
}
这种方案存在三个致命缺陷:
status字段没有合适索引时相比之下,RocketMQ方案具有明显优势:
实际压测数据显示:在10万QPS下单场景下,数据库负载降低83%,关单延迟标准差从29秒降至0.3秒
在RocketMQ 5.x中发送延时消息需要注意以下要点:
java复制MessageBuilder messageBuilder = new MessageBuilder();
Message message = messageBuilder
.setTopic("ORDER_TIMEOUT_TOPIC") // 建议按业务划分Topic
.setKeys(orderId) // 必须设置用于消息追踪
.setTag("TIMEOUT") // 二级分类,方便过滤
.setBody(orderId.getBytes()) // 建议只传必要字段
.setDeliveryTimestamp(deliverTimestamp) // 精确到毫秒的时间戳
.build();
// 建议设置发送超时时间(默认3秒)
SendOptions sendOptions = SendOptions.builder()
.setTimeout(5000)
.build();
producer.send(message, sendOptions);
参数选择经验:
消息重复消费是分布式系统必须考虑的问题。我们的关单服务需要实现幂等处理:
java复制private void checkAndCloseOrder(String orderId) {
// 使用SELECT FOR UPDATE避免并发问题
Order order = orderMapper.selectByIdForUpdate(orderId);
if (order == null) {
log.warn("订单不存在: {}", orderId);
return;
}
if ("PENDING_PAYMENT".equals(order.getStatus())) {
int updated = orderMapper.updateStatus(
orderId,
"PENDING_PAYMENT", // 旧状态
"CLOSED" // 新状态
);
if (updated > 0) {
inventoryService.releaseStock(orderId);
log.info("成功关闭订单: {}", orderId);
} else {
log.info("订单状态已变更: {}", orderId);
}
} else {
log.debug("订单已处理: {}", order.getStatus());
}
}
关键点说明:
RocketMQ的延时消息实现非常精妙,其核心流程如下:
消息写入阶段:
定时调度阶段:
消息投递阶段:

4.x版本:
5.x版本:
生产环境建议:如果使用4.x版本,应该根据业务需求调整messageDelayLevel配置。例如电商场景可以配置:1s 5s 10s 30s 1m 5m 10m 30m 1h 2h 6h 12h 24h
我们曾遇到促销活动导致延时消息堆积的问题,最终通过以下方案解决:
Broker端优化:
properties复制# 增加ScheduleMessageService线程数
scheduleMessageThreadPoolNums=8
# 调整每次调度处理的消息数
scheduleMessageBatchSize=32
消费端优化:
java复制// 使用批量消费模式
pushConsumer.registerMessageListener((messages, context) -> {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (MessageView msg : messages) {
futures.add(CompletableFuture.runAsync(() ->
processMessage(msg),
executorService
));
}
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> ConsumeResult.SUCCESS);
});
监控指标:
rocketmq_delay_message_latency:消息实际投递时间与预期时间的偏差rocketmq_schedule_message_count:各延时级别的消息堆积量通过三个层面的保障确保消息可靠性:
生产者保障:
java复制// 使用事务消息确保下单与发消息的原子性
TransactionSendResult result = producer.sendMessageInTransaction(message, arg);
if (result.getLocalTransactionState() != LocalTransactionState.COMMIT_MESSAGE) {
// 补偿逻辑
}
Broker保障:
消费者保障:
java复制// 消费失败时返回RECONSUME_LATER
try {
processMessage(message);
return ConsumeResult.SUCCESS;
} catch (Exception e) {
return ConsumeResult.RECONSUME_LATER;
}
延时消息不仅适用于订单超时,还可以用于:
在实际项目中,我们还将延时消息与工作流引擎结合,实现了灵活的任务调度系统。例如当审批流程超过24小时未处理,自动触发升级机制。