在微服务架构中,消息队列是系统解耦、削峰填谷的核心组件。但引入Kafka、RocketMQ等重型中间件往往意味着额外的运维成本和复杂度。如果你的业务量级适中,Redis完全可以承担起消息队列的职责,而且Spring Boot提供了优雅的集成方式。
本文将带你深入Redis实现消息队列的三种方式:List、Pub/Sub、Stream,从原理到Spring Boot实战,从坑点到最佳实践,帮你选型并落地。无需额外运维,轻松实现消息队列功能!
为什么选择Redis做消息队列?
在微服务架构中,消息队列的作用不言而喻:异步处理、流量削峰、系统解耦。但引入专业MQ(如Kafka、RabbitMQ)也带来了运维成本、学习曲线和资源开销。
而Redis作为业务系统标配的中间件,如果能在其基础上实现消息队列,无疑可以降低系统复杂度。Redis 5.0推出的Stream数据结构,更是让Redis在消息队列领域的成熟度大大提升。
适用场景
- ✅ 异步任务(如发送邮件、短信)
- ✅ 订单超时关闭(延迟队列)
- ✅ 日志收集
- ✅ 内部系统解耦(QPS < 1万)
Redis消息队列的三种实现方式(Spring Boot实战)
Redis实现消息队列经历了三个阶段:List → Pub/Sub → Stream。每种方式各有优劣,下面逐一剖析。
1. List:生产者-消费者模式(Spring Boot实现)
原理
利用Redis的List数据结构,生产者使用LPUSH将消息推入队列,消费者使用RPOP(或BRPOP)从队列中弹出消息。
Spring Boot实现
java
@Component
public class ListQueueProducer {
private final RedisTemplate<String, String> redisTemplate;
public ListQueueProducer(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void send(String message) {
redisTemplate.opsForList().leftPush("order_queue", message);
}
}
java
@Component
public class ListQueueConsumer {
private final RedisTemplate<String, String> redisTemplate;
public ListQueueConsumer(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Scheduled(fixedDelay = 500)
public void receive() {
String message = redisTemplate.opsForList().rightPop("order_queue");
if (message != null) {
// 处理消息
System.out.println("收到消息:" + message);
}
}
}
优缺点
| 优点 | 缺点 |
|---|---|
| ✅ 实现简单 | ❌ 消息丢失风险高(无ACK机制) |
| ✅ 低内存占用 | ❌ 不支持消费组,单消费者 |
| ✅ 低延迟 | ❌ 队列阻塞时CPU消耗高 |
适用场景
简单异步任务,允许少量消息丢失,且无需广播的场景。
2. Pub/Sub:广播模式(Spring Boot实现)
原理
Pub/Sub是Redis的发布订阅模型。生产者将消息发布到某个频道,所有订阅该频道的消费者都会收到消息。
Spring Boot实现
java
@Component
public class PubSubProducer {
private final RedisTemplate<String, String> redisTemplate;
public PubSubProducer(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void send(String message) {
redisTemplate.convertAndSend("order_channel", message);
}
}
java
@Component
public class PubSubConsumer {
@RedisListener(channels = "order_channel")
public void receive(String message) {
// 处理消息
System.out.println("收到消息:" + message);
}
}
优缺点
| 优点 | 缺点 |
|---|---|
| ✅ 实时性强 | ❌ 无消息持久化(消费者不在线则丢失) |
| ✅ 实现简单 | ❌ 无法回溯历史消息 |
| ✅ 低内存占用 | ❌ 无法保证消息可靠性 |
适用场景
实时通知、广播场景,对可靠性要求不高的场景。
3. Stream:真正可靠的消息队列(Redis 5.0+,Spring Boot实现)
原理
Stream是Redis 5.0引入的数据结构,专为消息队列设计,提供了持久化、消费组、消息确认、消息回溯等特性。
Spring Boot实现
java
@Component
public class StreamProducer {
private final RedisTemplate<String, String> redisTemplate;
public StreamProducer(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public String send(Map<String, String> fields) {
// * 表示由Redis自动生成消息ID
return redisTemplate.opsForStream().add("order_stream", fields);
}
}
java
@Component
public class StreamConsumer {
private final RedisTemplate<String, String> redisTemplate;
public StreamConsumer(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@PostConstruct
public void init() {
// 确保消费组存在(从头开始消费)
try {
redisTemplate.opsForStream().createGroup("order_stream", "group1", StreamOffset.fromStart());
} catch (Exception e) {
// 消费组已存在时忽略
}
}
@Scheduled(fixedDelay = 500)
public void consume() {
// 从消费组中读取消息
List<StreamMessage<String, String>> messages = redisTemplate.opsForStream().read(
Consumer.from("group1", "consumer1"),
StreamReadOptions.empty().count(10).block(Duration.ofMillis(0)),
StreamOffset.create("order_stream", StreamOffset.FromStart.START)
);
if (messages != null && !messages.isEmpty()) {
for (StreamMessage<String, String> message : messages) {
Map<String, String> fields = message.getValue();
String messageId = message.getId();
// 处理消息
System.out.println("收到消息:" + fields);
// 确认消息(防止丢失)
redisTemplate.opsForStream().acknowledge("order_stream", "group1", messageId);
}
}
}
}
优缺点
| 优点 | 缺点 |
|---|---|
| ✅ 消息持久化 | ❌ 内存占用较高(需定期修剪) |
| ✅ 消息确认(XACK) | ❌ 需要处理Pending队列 |
| ✅ 支持消费组 | ❌ 复杂度中高 |
| ✅ 消息回溯能力 |
适用场景
需要可靠消息、消费组、消息回溯的业务,如订单处理、任务调度等。
三种方案对比
| 特性 | List | Pub/Sub | Stream |
|---|---|---|---|
| 消息持久化 | ❌ | ❌ | ✅ |
| 消息确认 | ❌ | ❌ | ✅ |
| 消费组 | ❌ | ❌ | ✅ |
| 消息回溯 | ❌ | ❌ | ✅ |
| 内存占用 | 低 | 低 | 较高(需定期修剪) |
| 复杂度 | 低 | 低 | 中高 |
| 推荐场景 | 简单异步任务 | 实时广播 | 可靠消息、复杂业务 |
生产实践:使用Stream的完整案例
场景:订单超时自动关闭
假设我们需要在订单创建30分钟后自动关闭未支付的订单。
1. 添加延时消息
java
// 生产者:添加延时消息
Map<String, String> msg = new HashMap<>();
msg.put("order_id", "123");
msg.put("execute_time", String.valueOf(System.currentTimeMillis() + 30 * 60 * 1000));
streamProducer.send(msg);
2. 消费者处理逻辑
java
@Scheduled(fixedDelay = 5000)
public void processDelayedMessages() {
List<StreamMessage<String, String>> messages = redisTemplate.opsForStream().read(
Consumer.from("delay_group", "delay_consumer"),
StreamReadOptions.empty().count(10).block(Duration.ofMillis(0)),
StreamOffset.create("order_delay_stream", StreamOffset.FromStart.START)
);
if (messages != null && !messages.isEmpty()) {
for (StreamMessage<String, String> message : messages) {
Map<String, String> fields = message.getValue();
long executeTime = Long.parseLong(fields.get("execute_time"));
if (System.currentTimeMillis() >= executeTime) {
// 执行超时关闭
closeOrder(fields.get("order_id"));
// 确认消息
redisTemplate.opsForStream().acknowledge("order_delay_stream", "delay_group", message.getId());
} else {
// 未到时间,重新放回队列
redisTemplate.opsForStream().add("order_delay_stream", fields);
redisTemplate.opsForStream().acknowledge("order_delay_stream", "delay_group", message.getId());
redisTemplate.opsForStream().delete("order_delay_stream", message.getId());
}
}
}
}
3. 监控Pending队列(防止消息堆积)
java
@Scheduled(fixedDelay = 60000)
public void checkPendingMessages() {
List<StreamPendingEntry> pending = redisTemplate.opsForStream().pending("order_stream", "group1");
if (pending != null && !pending.isEmpty()) {
// 将超时未处理的消息分配给其他消费者
for (StreamPendingEntry entry : pending) {
redisTemplate.opsForStream().claim("order_stream", "group1", "new_consumer", 60000, entry.getId());
}
}
}
总结与选型建议
Redis消息队列的选择,取决于你的业务需求:
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 简单异步任务 | List | 实现最简单,但丢失消息风险高 |
| 实时广播 | Pub/Sub | 不支持持久化,适合对可靠性要求不高的场景 |
| 可靠消息、复杂业务 | Stream | 支持持久化、ACK、消费组,是生产环境首选 |
重要提醒
- 消息幂等性:无论哪种方案,都要考虑消息幂等性(因为可能重复消费)。
- 监控:监控队列长度、消费者健康状态,防止积压。
- 内存管理:Stream会持久化所有消息,需根据业务设置消息最大长度或定期修剪。
- 高吞吐量:对于极高吞吐量(QPS > 1万)的场景,仍建议使用专业MQ(如Kafka)。
结语
Redis作为消息队列,凭借其轻量级、低延迟和简单运维的优势,已经成为众多中小型项目的首选方案。Spring Boot的优雅集成让Redis消息队列的实现变得如此简单。
"不要为了解决一个问题而引入另一个问题。"
用好Redis,让消息队列不再成为你的负担。
希望本文能帮助你快速上手并避开常见坑点。如果这篇文章对你有帮助,欢迎点赞、收藏、转发,让更多开发者受益!
关注我,持续输出后端干货,一起成长!