电商系统中订单超时自动取消是一个经典场景。传统方案如数据库轮询或定时任务存在性能瓶颈和时效性问题,而引入RabbitMQ、Kafka等专业消息队列又可能带来架构复杂度提升。Redis 5.0引入的Stream数据结构,凭借其消费者组、消息回溯和ACK机制,为这类场景提供了轻量级解决方案。
本文将基于SpringBoot 2.7+和Redis 6.2,演示如何构建高可靠的订单超时处理系统。不同于简单API调用示例,我们会重点探讨生产环境中可能遇到的消息堆积、消费失败重试等实际问题,并给出完整工程实践方案。
数据库轮询方案的典型实现方式:
java复制@Scheduled(fixedDelay = 5000)
public void checkExpiredOrders() {
List<Order> orders = orderMapper.selectExpiredOrders();
orders.forEach(this::cancelOrder);
}
这种方案存在三个明显缺陷:
Redis Stream方案的核心优势:
| 特性 | 数据库轮询 | Redis Stream |
|---|---|---|
| 实时性 | 低 | 高 |
| 吞吐量 | 有限 | 10万+/秒 |
| 消息持久化 | 无 | 支持 |
| 消费者组 | 不支持 | 支持 |
| 失败重试机制 | 需自行实现 | 内置 |
订单超时处理的完整流程包含四个关键组件:
mermaid复制graph TD
A[订单服务] -->|XADD order:stream| B[Redis Stream]
B -->|XREADGROUP| C[Stream处理器]
C -->|XPENDING| D[监控服务]
D -->|异常报警| E[报警系统]
首先添加必要的依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
配置Lettuce连接池:
yaml复制spring:
redis:
host: redis-cluster.example.com
lettuce:
pool:
max-active: 200
max-idle: 50
min-idle: 10
提示:生产环境建议使用Redis Cluster模式,配置多个节点地址提高可用性
订单创建时发送延时消息:
java复制public class OrderService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void createOrder(Order order) {
// 保存订单到数据库
orderMapper.insert(order);
// 创建Stream消息
Map<String, String> message = Map.of(
"orderId", order.getId(),
"userId", order.getUserId(),
"createTime", String.valueOf(order.getCreateTime().getTime())
);
MapRecord<String, String, String> record = StreamRecords
.newRecord()
.in("order:stream")
.ofMap(message)
.withId(RecordId.autoGenerate());
redisTemplate.opsForStream().add(record);
}
}
创建支持消费者组的监听容器:
java复制@Configuration
public class StreamConfig {
@Bean
public StreamMessageListenerContainer<String, MapRecord<String, String, String>> container(
RedisConnectionFactory factory) {
StreamMessageListenerContainerOptions<String, MapRecord<String, String, String>> options =
StreamMessageListenerContainerOptions.builder()
.pollTimeout(Duration.ofSeconds(1))
.batchSize(10)
.errorHandler(e -> log.error("Stream消费异常", e))
.build();
return StreamMessageListenerContainer.create(factory, options);
}
@Bean
public Subscription subscription(
StreamMessageListenerContainer<String, MapRecord<String, String, String>> container,
OrderExpireListener listener) {
return container.receive(
Consumer.from("order-group", "consumer-1"),
StreamOffset.create("order:stream", ReadOffset.lastConsumed()),
listener
);
}
}
实现带异常处理的订单超时逻辑:
java复制@Slf4j
@Component
public class OrderExpireListener implements StreamListener<String, MapRecord<String, String, String>> {
@Autowired
private OrderService orderService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void onMessage(MapRecord<String, String, String> message) {
try {
String orderId = message.getValue().get("orderId");
Order order = orderService.getById(orderId);
if (order.getStatus() == OrderStatus.PAID) {
log.info("订单{}已支付,跳过处理", orderId);
return;
}
orderService.cancelOrder(orderId);
redisTemplate.opsForStream().acknowledge("order:stream", "order-group", message.getId());
} catch (Exception e) {
log.error("处理订单超时失败: {}", message, e);
// 失败消息会留在Pending列表等待后续处理
}
}
}
定时检查并重新处理失败消息:
java复制@Scheduled(fixedRate = 60000)
public void handlePendingMessages() {
PendingMessages pending = redisTemplate.opsForStream()
.pending("order:stream", "order-group",
Range.unbounded(), 100);
pending.forEach(msg -> {
List<MapRecord<String, String, String>> records = redisTemplate.opsForStream()
.range("order:stream",
Range.closed(msg.getIdAsString(), msg.getIdAsString()),
Limit.limit().count(1));
if (!records.isEmpty()) {
onMessage(records.get(0)); // 重新处理
}
});
}
针对高并发场景的优化参数:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| lettuce.pool.max-active | 200-500 | 根据实例数量调整 |
| pollTimeout | 1-3秒 | 平衡实时性和资源消耗 |
| batchSize | 5-20 | 单次拉取消息数量 |
| concurrency | CPU核心数*2 | 消费者线程数 |
关键监控指标实现示例:
java复制@Bean
public MeterRegistryCustomizer<MeterRegistry> metrics() {
return registry -> {
Gauge.builder("redis.stream.pending", () -> {
PendingMessagesSummary summary = redisTemplate.opsForStream()
.pending("order:stream", "order-group");
return summary.getTotalPendingMessages();
}).register(registry);
};
}
在实际项目中,我们通过这套方案将订单超时处理延迟从原来的分钟级降低到秒级,同时系统资源消耗降低了60%。特别是在大促期间,Redis Stream表现出了良好的稳定性,没有出现消息堆积情况。