📖目录
- 引言:快递分拣站的智慧
- [1. 为什么需要Redis队列?------生活中的"快递"启示](#1. 为什么需要Redis队列?——生活中的"快递"启示)
- [2. Redis队列原理:4个核心认知](#2. Redis队列原理:4个核心认知)
- [3. Redis队列核心实现方式详解(10+个命令示例)](#3. Redis队列核心实现方式详解(10+个命令示例))
-
- [✅ 3.1. 基于List的队列实现](#✅ 3.1. 基于List的队列实现)
- [✅ 3.2. 基于Sorted-Set的延时队列](#✅ 3.2. 基于Sorted-Set的延时队列)
- [✅ 3.3. PUB/SUB订阅发布模式](#✅ 3.3. PUB/SUB订阅发布模式)
- [✅ 3.4. 基于Stream的高性能队列](#✅ 3.4. 基于Stream的高性能队列)
- [4. Spring Boot中Redis队列的实战应用(5+个企业级示例)](#4. Spring Boot中Redis队列的实战应用(5+个企业级示例))
-
- [4.1 通用配置准备(所有示例共用)](#4.1 通用配置准备(所有示例共用))
-
- [4.1.1. Maven依赖(pom.xml)](#4.1.1. Maven依赖(pom.xml))
- [4.1.2. application.yml配置](#4.1.2. application.yml配置)
- [4.1.3. Redis配置类(统一序列化方案)](#4.1.3. Redis配置类(统一序列化方案))
- [5. 代码实例](#5. 代码实例)
-
- [5.1. List队列:订单处理实战(含测试类)](#5.1. List队列:订单处理实战(含测试类))
- [5.1.1. 服务层代码](#5.1.1. 服务层代码)
- [5.1.2. 测试类(直接运行验证)](#5.1.2. 测试类(直接运行验证))
- [5.1.3. 执行结果](#5.1.3. 执行结果)
- [5.2. Sorted-Set延时队列:优惠券过期提醒](#5.2. Sorted-Set延时队列:优惠券过期提醒)
-
- [5.2.1. 服务层代码](#5.2.1. 服务层代码)
- [5.2.2. 测试类(直接运行验证)](#5.2.2. 测试类(直接运行验证))
- [5.2.3. 执行结果](#5.2.3. 执行结果)
- [5. 3. PUB/SUB:用户行为实时通知](#5. 3. PUB/SUB:用户行为实时通知)
-
- [5.3.1. 配置类(消息监听容器)](#5.3.1. 配置类(消息监听容器))
- [5.3.2. 消息接收器](#5.3.2. 消息接收器)
- [5.3.3. 服务层(发布消息)](#5.3.3. 服务层(发布消息))
- [5.3.4. 测试类(直接运行验证)](#5.3.4. 测试类(直接运行验证))
- [5.3.5. 执行结果](#5.3.5. 执行结果)
- [5. 4. Stream队列:高可靠订单处理](#5. 4. Stream队列:高可靠订单处理)
-
- [5.4.1. 服务层接口(参考文章风格)](#5.4.1. 服务层接口(参考文章风格))
- [5.4.2. 服务层实现(修正核心逻辑)](#5.4.2. 服务层实现(修正核心逻辑))
- [5.4.3. 测试类(单实例验证)](#5.4.3. 测试类(单实例验证))
- [5.4.4. 执行结果](#5.4.4. 执行结果)
- [5.5. Stream消费者组:多实例负载均衡实战](#5.5. Stream消费者组:多实例负载均衡实战)
-
- [5.5.1. 服务层(扩展自上一示例)](#5.5.1. 服务层(扩展自上一示例))
- [5.5.2. 测试类(模拟3个实例)](#5.5.2. 测试类(模拟3个实例))
- [5.5.3. 执行结果](#5.5.3. 执行结果)
- [6. Spring Boot集成关键总结](#6. Spring Boot集成关键总结)
- [7. Redis队列的性能对比与最佳实践](#7. Redis队列的性能对比与最佳实践)
-
- [7.1性能对比(Redis 7.4.7测试)](#7.1性能对比(Redis 7.4.7测试))
- [8. 往期回顾](#8. 往期回顾)
- [9. 经典书推荐](#9. 经典书推荐)
- [10. 参考资料](#10. 参考资料)
- [11. 代码示例已开源,欢迎下载使用!](#11. 代码示例已开源,欢迎下载使用!)
引言:快递分拣站的智慧
想象一下,你在电商平台上下单后,系统需要处理各种操作:生成订单、扣减库存、发送通知、更新用户积分... 如果这些操作都按顺序执行,就像一个人在快递站里手忙脚乱地处理所有包裹,效率极低。
而Redis队列就像一个智能快递分拣站,它能将这些操作自动分解、排序,然后由不同的"快递员"(线程)同时处理,确保每个包裹都能准确、高效地送达目的地。Redis 8的队列机制,正是为了解决这种"多任务并行处理"的场景,让系统像快递分拣站一样高效运转。
1. 为什么需要Redis队列?------生活中的"快递"启示
在电商系统中,你下单后需要完成多个操作:
- 生成订单
- 扣减库存
- 发送短信通知
- 更新用户积分
- 记录日志
如果这些操作按顺序执行,就像快递员一个一个处理包裹,效率低下。而Redis队列就像快递分拣站,能将这些操作放入队列,由多个"快递员"并行处理,大大提高效率。
💡 关键区别:Redis队列不是"保险箱",而是"智能分拣站"。它保证消息的顺序性,但不保证消息的回滚(如果处理失败)。
2. Redis队列原理:4个核心认知
| 概念 | Redis队列 | 传统消息队列 | 大白话解释 |
|---|---|---|---|
| 顺序性 | ✅ 保证消息顺序 | ✅ 保证消息顺序 | 你寄的包裹按顺序分拣,不会先处理后寄的包裹 |
| 可靠性 | ⚠️ 依赖持久化配置 | ✅ 保证消息可靠 | 队列中的消息可能丢失,取决于Redis配置 |
| 扩展性 | ✅ 支持水平扩展 | ✅ 支持水平扩展 | 可以添加更多"快递员"处理更多包裹 |
| 应用场景 | ✅ 订单处理、通知推送 | ✅ 企业级消息传递 | 适合电商、社交等需要异步处理的场景 |
🌰 生活案例:你和朋友同时在淘宝下单限量版球鞋
- 你使用Redis队列:
XADD queue:shoes * product_id "1001" quantity "1" - 朋友同时下单:
XADD queue:shoes * product_id "1001" quantity "1" - 系统会按顺序处理两个订单,确保不会超卖
3. Redis队列核心实现方式详解(10+个命令示例)
✅ 3.1. 基于List的队列实现
Redis的List数据结构非常适合实现简单的队列,支持LPUSH和RPOP(或BRPOP)操作。
bash
# 1. 添加消息到队列(左侧入队)
127.0.0.1:6379> LPUSH queue:orders "order_1001"
(integer) 1
# 2. 添加多个消息到队列
127.0.0.1:6379> LPUSH queue:orders "order_1002" "order_1003"
(integer) 3
# 3. 从队列左侧取出消息(阻塞等待)
127.0.0.1:6379> BRPOP queue:orders 5
1) "queue:orders"
2) "order_1003"
# 4. 查看队列剩余消息
127.0.0.1:6379> LRANGE queue:orders 0 -1
1) "order_1001"
2) "order_1002"
# 5. 消息出队后,队列长度减1
127.0.0.1:6379> LLEN queue:orders
(integer) 2
# 6. 清空队列
127.0.0.1:6379> DEL queue:orders
(integer) 1
✅ 3.2. 基于Sorted-Set的延时队列
Sorted-Set可以实现延时队列,通过分数控制消息的处理时间。
bash
# 1. 添加带时间戳的订单到队列
127.0.0.1:6379> ZADD queue:delayed_orders 1706784000 "order_1001"
(integer) 1
# 2. 添加另一个订单,时间更晚
127.0.0.1:6379> ZADD queue:delayed_orders 1706785000 "order_1002"
(integer) 1
# 3. 获取当前可以处理的订单(分数小于当前时间)
127.0.0.1:6379> ZRANGEBYSCORE queue:delayed_orders 0 1706784000
1) "order_1001"
# 4. 处理订单后,从队列中移除
127.0.0.1:6379> ZREM queue:delayed_orders "order_1001"
(integer) 1
# 5. 查看剩余订单
127.0.0.1:6379> ZRANGE queue:delayed_orders 0 -1
1) "order_1002"
# 6. 设置订单处理超时时间
127.0.0.1:6379> ZADD queue:timeout_orders 1706785000 "order_1003"
(integer) 1
# 7. 获取超时订单
127.0.0.1:6379> ZRANGEBYSCORE queue:timeout_orders 0 1706785000
1) "order_1003"
# 8. 获取所有订单(按分数排序)
127.0.0.1:6379> ZRANGE queue:delayed_orders 0 -1 WITHSCORES
1) "order_1002"
2) "1706785000"
✅ 3.3. PUB/SUB订阅发布模式
PUB/SUB模式适合一对多的消息发布,适合通知类场景。
bash
# 1. 订阅频道
127.0.0.1:6379> SUBSCRIBE order:notifications
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "order:notifications"
3) (integer) 1
# 退出订阅模式
UNSUBSCRIBE order:notifications
1) "unsubscribe"
2) "order:notifications"
3) (integer) 0
# 2. 发布消息到频道
127.0.0.1:6379> PUBLISH order:notifications "Order 1001 processed"
(integer) 1
# 3. 订阅多个频道
127.0.0.1:6379> SUBSCRIBE order:notifications user:updates
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "order:notifications"
3) (integer) 1
1) "subscribe"
2) "user:updates"
3) (integer) 2
# 4. 发布消息到多个频道
127.0.0.1:6379> PUBLISH order:notifications "Order 1002 processed"
(integer) 1
127.0.0.1:6379> PUBLISH user:updates "User 1001 balance updated"
(integer) 2
# 5. 取消订阅
127.0.0.1:6379> UNSUBSCRIBE order:notifications
1) "unsubscribe"
2) "order:notifications"
3) (integer) 1
# 6. 查看当前订阅的频道
127.0.0.1:6379> PUBSUB CHANNELS
1) "user:updates"
✅ 3.4. 基于Stream的高性能队列
Stream是Redis 5.0+引入的,支持多播的可持久化消息队列,借鉴了Kafka的设计。
bash
# 1. 创建Stream并添加消息
127.0.0.1:6379> XADD orders_stream * order_id "1001" product_id "P1001" quantity "2"
"1706784000-0"
# 2. 添加更多消息
127.0.0.1:6379> XADD orders_stream * order_id "1002" product_id "P1002" quantity "1"
"1706784001-0"
# 3. 查看Stream中的消息
127.0.0.1:6379> XRANGE orders_stream - +
1) 1) "1706784000-0"
2) 1) "order_id"
2) "1001"
3) "product_id"
4) "P1001"
5) "quantity"
6) "2"
2) 1) "1706784001-0"
2) 1) "order_id"
2) "1002"
3) "product_id"
4) "P1002"
5) "quantity"
6) "1"
# 4. 创建消费者组
127.0.0.1:6379> XGROUP CREATE orders_stream order_group 0
OK
# 5. 读取消息(消费者组)
127.0.0.1:6379> XREADGROUP GROUP order_group consumer1 STREAMS orders_stream >
1) 1) "orders_stream"
2) 1) 1) "1706784000-0"
2) 1) "order_id"
2) "1001"
3) "product_id"
4) "P1001"
5) "quantity"
6) "2"
# 6. 确认消息已处理
127.0.0.1:6379> XACK orders_stream order_group 1706784000-0
(integer) 1
# 7. 查看Pending消息(语法错误)
127.0.0.1:6379> XPENDING orders_stream order_group - +
1) (integer) 0
2) "1706784000-0"
3) "1706784001-0"
4) 1) 1) "consumer1"
2) "1"
# 8. 删除消息
127.0.0.1:6379> XDEL orders_stream 1706784000-0
(integer) 1
4. Spring Boot中Redis队列的实战应用(5+个企业级示例)
💡 核心提示:以下所有示例均基于Spring Boot 3.2 + Spring Data Redis 3.2 + Lettuce客户端,已通过Redis 7.2实测验证。所有代码可直接复制到Spring Boot项目中运行!
4.1 通用配置准备(所有示例共用)
4.1.1. Maven依赖(pom.xml)
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redis连接池(Lettuce已内置,但显式声明更清晰) -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
4.1.2. application.yml配置
yaml
spring:
redis:
host: localhost
port: 6379
password: "" # 无密码留空
database: 0
timeout: 5000ms
lettuce:
pool:
max-active: 8 # 最大连接数
max-idle: 8 # 最大空闲连接
min-idle: 2 # 最小空闲连接
max-wait: 2000ms # 连接池最大等待时间
# Stream消费者组自动创建配置(关键!)
stream:
consumer-group:
create-on-startup: true
4.1.3. Redis配置类(统一序列化方案)
java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用String序列化器(避免乱码)
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(stringRedisSerializer);
template.afterPropertiesSet();
return template;
}
// Stream专用操作模板(带对象序列化)
@Bean
public RedisTemplate<String, Object> streamRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
5. 代码实例
5.1. List队列:订单处理实战(含测试类)
5.1.1. 服务层代码
java
@Service
@Slf4j
public class OrderListQueueService {
private static final String ORDER_QUEUE = "biz:order:queue";
private final RedisTemplate<String, String> redisTemplate;
public OrderListQueueService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 生产者:添加订单到队列
*/
public void enqueueOrder(String orderId) {
redisTemplate.opsForList().rightPush(ORDER_QUEUE, orderId);
log.info("✅ 订单入队成功: {}", orderId);
}
/**
* 消费者:阻塞式处理订单(带超时)
*/
@Async // 异步处理(需在启动类加@EnableAsync)
public void processOrders() {
while (true) {
String orderId = redisTemplate.opsForList().leftPop(ORDER_QUEUE, Duration.ofSeconds(5));
if (orderId != null) {
log.info("🚚 正在处理订单: {}", orderId);
// 模拟业务处理(扣库存、发短信等)
try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
log.info("✅ 订单处理完成: {}", orderId);
} else {
log.debug("⏳ 队列为空,等待新订单...");
}
}
}
/**
* 获取队列长度(监控用)
*/
public Long getQueueSize() {
return redisTemplate.opsForList().size(ORDER_QUEUE);
}
}
5.1.2. 测试类(直接运行验证)
java
@SpringBootTest
@Slf4j
class OrderListQueueServiceTest {
@Autowired
private OrderListQueueService orderService;
@Test
void testOrderQueue() throws InterruptedException {
// 模拟5个订单入队
for (int i = 1; i <= 5; i++) {
orderService.enqueueOrder("ORDER_20260201_" + i);
}
// 启动消费者线程
new Thread(orderService::processOrders).start();
// 等待处理完成
Thread.sleep(3000);
// 验证队列是否清空
Long size = orderService.getQueueSize();
log.info("📌 队列剩余订单数: {}", size);
Assertions.assertEquals(0L, size, "队列应为空");
// 【执行结果预留位】
// 2026-02-01 14:20:15.123 INFO ✅ 订单入队成功: ORDER_20260201_1
// 2026-02-01 14:20:15.125 INFO ✅ 订单入队成功: ORDER_20260201_2
// ...(中间省略)
// 2026-02-01 14:20:16.310 INFO 🚚 正在处理订单: ORDER_20260201_1
// 2026-02-01 14:20:16.512 INFO ✅ 订单处理完成: ORDER_20260201_1
// ...(后续订单处理日志)
// 2026-02-01 14:20:18.105 INFO 📌 队列剩余订单数: 0
}
}
5.1.3. 执行结果
✅ 订单入队成功: ORDER_20260201_1
✅ 订单入队成功: ORDER_20260201_2
✅ 订单入队成功: ORDER_20260201_3
✅ 订单入队成功: ORDER_20260201_4
✅ 订单入队成功: ORDER_20260201_5
🚚 正在处理订单: ORDER_20260201_1
✅ 订单处理完成: ORDER_20260201_1
🚚 正在处理订单: ORDER_20260201_2
✅ 订单处理完成: ORDER_20260201_2
🚚 正在处理订单: ORDER_20260201_3
✅ 订单处理完成: ORDER_20260201_3
🚚 正在处理订单: ORDER_20260201_4
✅ 订单处理完成: ORDER_20260201_4
🚚 正在处理订单: ORDER_20260201_5
✅ 订单处理完成: ORDER_20260201_5
📌 队列剩余订单数: 0
5.2. Sorted-Set延时队列:优惠券过期提醒
5.2.1. 服务层代码
java
@Service
@Slf4j
public class CouponDelayQueueService {
private static final String COUPON_QUEUE = "biz:coupon:delay";
private final RedisTemplate<String, String> redisTemplate;
public CouponDelayQueueService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 添加延时任务(单位:秒)
* @param couponId 优惠券ID
* @param delaySeconds 延迟秒数
*/
public void scheduleExpire(String couponId, long delaySeconds) {
long expireTimestamp = System.currentTimeMillis() / 1000 + delaySeconds;
redisTemplate.opsForZSet().add(COUPON_QUEUE, couponId, expireTimestamp);
log.info("⏰ 优惠券{}将在{}秒后过期提醒", couponId, delaySeconds);
}
/**
* 定时扫描过期优惠券(建议用@Scheduled注解)
*/
public void checkExpiredCoupons() {
long now = System.currentTimeMillis() / 1000;
// 获取所有已到时间的优惠券
Set<String> expiredCoupons = redisTemplate.opsForZSet()
.rangeByScore(COUPON_QUEUE, 0, now);
if (expiredCoupons != null && !expiredCoupons.isEmpty()) {
for (String couponId : expiredCoupons) {
log.warn("❗ 优惠券{}已过期,触发用户提醒", couponId);
// TODO: 发送站内信/短信提醒用户
redisTemplate.opsForZSet().remove(COUPON_QUEUE, couponId);
}
}
}
}
5.2.2. 测试类(直接运行验证)
java
@SpringBootTest
@Slf4j
class CouponDelayQueueServiceTest {
@Autowired
private CouponDelayQueueService couponService;
@Test
void testDelayQueue() throws InterruptedException {
// 添加3个不同延迟的优惠券
couponService.scheduleExpire("COUPON_1001", 2); // 2秒后过期
couponService.scheduleExpire("COUPON_1002", 4); // 4秒后过期
couponService.scheduleExpire("COUPON_1003", 6); // 6秒后过期
// 模拟定时任务扫描(每2秒扫描一次)
for (int i = 0; i < 4; i++) {
log.info("🔍 第{}次扫描过期优惠券", i + 1);
couponService.checkExpiredCoupons();
Thread.sleep(2000);
}
// 【执行结果预留位】
// 2026-02-01 14:25:10.111 INFO ⏰ 优惠券COUPON_1001将在2秒后过期提醒
// 2026-02-01 14:25:10.113 INFO ⏰ 优惠券COUPON_1002将在4秒后过期提醒
// 2026-02-01 14:25:10.115 INFO ⏰ 优惠券COUPON_1003将在6秒后过期提醒
// 2026-02-01 14:25:12.120 INFO 🔍 第1次扫描过期优惠券
// 2026-02-01 14:25:12.125 WARN ❗ 优惠券COUPON_1001已过期,触发用户提醒
// 2026-02-01 14:25:14.130 INFO 🔍 第2次扫描过期优惠券
// 2026-02-01 14:25:14.132 WARN ❗ 优惠券COUPON_1002已过期,触发用户提醒
// ...(后续扫描日志)
}
}
5.2.3. 执行结果
⏰ 优惠券COUPON_1001将在2秒后过期提醒
⏰ 优惠券COUPON_1002将在4秒后过期提醒
⏰ 优惠券COUPON_1003将在6秒后过期提醒
🔍 第1次扫描过期优惠券
🔍 第2次扫描过期优惠券
❗ 优惠券COUPON_1001已过期,触发用户提醒
🔍 第3次扫描过期优惠券
❗ 优惠券COUPON_1002已过期,触发用户提醒
🔍 第4次扫描过期优惠券
❗ 优惠券COUPON_1003已过期,触发用户提醒
5. 3. PUB/SUB:用户行为实时通知
5.3.1. 配置类(消息监听容器)
java
@Configuration
public class RedisPubSubConfig {
@Bean
public RedisMessageListenerContainer container(
RedisConnectionFactory connectionFactory,
MessageListenerAdapter userActionListener) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 订阅"用户行为"频道
container.addMessageListener(userActionListener, new PatternTopic("user:action:*"));
return container;
}
@Bean
MessageListenerAdapter userActionListener(UserActionReceiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
}
5.3.2. 消息接收器
java
@Component
@Slf4j
public class UserActionReceiver {
/**
* 处理用户行为消息(方法名需与MessageListenerAdapter中指定一致)
*/
public void receiveMessage(String message, String channel) {
log.info("📬 收到频道[{}]消息: {}", channel, message);
// TODO: 实时分析用户行为(如:记录活跃度、触发推荐)
if (channel.contains("login")) {
log.info("💡 检测到用户登录行为,更新活跃状态");
}
}
}
5.3.3. 服务层(发布消息)
java
@Service
@Slf4j
public class UserActionService {
private final RedisTemplate<String, String> redisTemplate;
public UserActionService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 发布用户登录事件
*/
public void publishLogin(String userId) {
String channel = "user:action:login";
String message = String.format("{\"userId\":\"%s\",\"time\":\"%s\"}",
userId, LocalDateTime.now());
redisTemplate.convertAndSend(channel, message);
log.info("📤 已发布登录事件到频道: {}", channel);
}
}
5.3.4. 测试类(直接运行验证)
java
@SpringBootTest
@Slf4j
class UserActionServiceTest {
@Autowired
private UserActionService userActionService;
@Test
void testPubSub() throws InterruptedException {
// 模拟用户登录
userActionService.publishLogin("USER_8888");
// 等待消息处理(实际项目中无需等待)
Thread.sleep(1000);
// 【执行结果预留位】
// 2026-02-01 14:30:05.210 INFO 📤 已发布登录事件到频道: user:action:login
// 2026-02-01 14:30:05.215 INFO 📬 收到频道[user:action:login]消息: {"userId":"USER_8888","time":"2026-02-01T14:30:05.212"}
// 2026-02-01 14:30:05.217 INFO 💡 检测到用户登录行为,更新活跃状态
}
}
5.3.5. 执行结果
📤 已发布登录事件到频道: user:action:login
📬 收到频道[user:action:*]消息: {"userId":"USER_8888","time":"2026-02-01T16:40:44.114"}
5. 4. Stream队列:高可靠订单处理
5.4.1. 服务层接口(参考文章风格)
java
public interface RedisStreamService {
/** 生产消息 */
RecordId xAdd(String key, Map<String, Object> map);
/** 创建消费组 */
String xGroupCreate(String key, String group);
/** 读取消息(带消费组) */
List<MapRecord<String, Object, Object>> xReadGroup(
String group,
String consumer,
StreamReadOptions options,
StreamOffset<String>... offsets
);
/** 消息确认 */
Long xAck(String key, String group, String... recordIds);
/** 查看待处理消息 */
PendingMessagesSummary xPending(String key, String group);
}
5.4.2. 服务层实现(修正核心逻辑)
java
@Service
@Slf4j
public class RedisStreamServiceImpl implements RedisStreamService {
private static final String ORDER_STREAM = "stream:order:processing";
private static final String CONSUMER_GROUP = "order-group";
private final RedisTemplate<String, Object> redisTemplate;
private final StreamOperations<String, Object, Object> streamOperations;
public RedisStreamServiceImpl(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
this.streamOperations = redisTemplate.opsForStream();
}
@Override
public RecordId xAdd(String key, Map<String, Object> map) {
return streamOperations.add(key, map);
}
@Override
public String xGroupCreate(String key, String group) {
try {
// 使用ReadOffset.latest()从最新消息开始(避免处理历史消息)
return streamOperations.createGroup(key, ReadOffset.latest(), group);
} catch (Exception e) {
if (e.getMessage().contains("BUSYGROUP")) {
log.info("消费者组[{}]已存在", group);
return "OK";
}
throw e;
}
}
@Override
public List<MapRecord<String, Object, Object>> xReadGroup(
String group,
String consumer,
StreamReadOptions options,
StreamOffset<String>... offsets) {
Consumer consumerObj = Consumer.from(group, consumer);
return streamOperations.read(consumerObj, options, offsets);
}
@Override
public Long xAck(String key, String group, String... recordIds) {
return streamOperations.acknowledge(key, group, recordIds);
}
@Override
public PendingMessagesSummary xPending(String key, String group) {
return streamOperations.pending(key, group);
}
/**
* 初始化消费者组(应用启动时调用)
*/
@PostConstruct
public void initConsumerGroup() {
xGroupCreate(ORDER_STREAM, CONSUMER_GROUP);
}
/**
* 添加订单到Stream
*/
public void addOrder(String orderId, String productId, Integer quantity) {
Map<String, Object> orderData = new HashMap<>();
orderData.put("orderId", orderId);
orderData.put("productId", productId);
orderData.put("quantity", quantity);
orderData.put("timestamp", System.currentTimeMillis());
RecordId messageId = xAdd(ORDER_STREAM, orderData);
log.info("✅ 订单[{}]写入Stream,ID: {}", orderId, messageId);
}
/**
* 处理订单(单实例消费者)
*/
public void processOrders(String consumerName) {
int maxRetries = 3; // 最大重试次数
int retryInterval = 2000; // 重试间隔时间(毫秒)
while (true) {
try {
// 配置读取选项:每次最多10条,阻塞5秒
StreamReadOptions options = StreamReadOptions.empty()
.count(10)
.block(Duration.ofSeconds(5));
// 从>位置读取新消息(不读取pending消息)
StreamOffset<String> offset = StreamOffset.create(ORDER_STREAM, ReadOffset.lastConsumed());
List<MapRecord<String, Object, Object>> records =
xReadGroup(CONSUMER_GROUP, consumerName, options, offset);
if (records != null && !records.isEmpty()) {
for (MapRecord<String, Object, Object> record : records) {
try {
Map<Object, Object> data = record.getValue();
String orderId = (String) data.get("orderId");
log.info("🚚 [{}] 处理订单: {}", consumerName, orderId);
// 模拟业务处理
Thread.sleep(150);
// 手动ACK
xAck(ORDER_STREAM, CONSUMER_GROUP, record.getId().toString());
log.info("✅ [{}] 订单[{}] ACK完成", consumerName, orderId);
} catch (Exception e) {
log.error("[{}] 处理订单失败: {}", consumerName, record.getId(), e);
// 失败消息会留在PEL,可后续重试
}
}
} else {
log.debug("[{}] 未获取到新消息,继续等待...", consumerName);
}
} catch (RedisCommandTimeoutException e) {
log.error("[{}] Redis命令超时,重试中...", consumerName, e);
for (int i = 0; i < maxRetries; i++) {
try {
Thread.sleep(retryInterval);
break; // 如果成功,则跳出重试循环
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (maxRetries > 0) {
log.error("[{}] 重试次数已用尽,放弃本次处理", consumerName);
}
} catch (Exception e) {
log.error("[{}] 消费异常", consumerName, e);
try { Thread.sleep(2000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
}
}
}
}
5.4.3. 测试类(单实例验证)
java
@SpringBootTest
@Slf4j
class RedisStreamServiceImplTest {
@Autowired
private RedisStreamServiceImpl streamService;
@Test
void testSingleConsumer() throws InterruptedException {
// 添加3个订单
streamService.addOrder("STREAM_ORDER_1", "PROD_A", 2);
streamService.addOrder("STREAM_ORDER_2", "PROD_B", 1);
streamService.addOrder("STREAM_ORDER_3", "PROD_C", 3);
// 启动单个消费者
new Thread(() -> streamService.processOrders("consumer-single")).start();
// 等待处理完成
Thread.sleep(8000);
// 【执行结果预留位】
// 2026-02-01 15:10:05.111 INFO ✅ 消费者组[order-group]已存在
// 2026-02-01 15:10:05.120 INFO ✅ 订单[STREAM_ORDER_1]写入Stream,ID: 1706784910120-0
// ...(其他订单写入日志)
// 2026-02-01 15:10:10.210 INFO 🚚 [consumer-single] 处理订单: STREAM_ORDER_1
// 2026-02-01 15:10:10.365 INFO ✅ [consumer-single] 订单[STREAM_ORDER_1] ACK完成
// ...(后续订单处理日志)
// 2026-02-01 15:10:11.020 DEBUG [consumer-single] 未获取到新消息,继续等待...
}
}
5.4.4. 执行结果
✅ 订单[STREAM_ORDER_1]写入Stream,ID: 1769948273577-0
✅ 订单[STREAM_ORDER_2]写入Stream,ID: 1769948273651-0
✅ 订单[STREAM_ORDER_3]写入Stream,ID: 1769948273724-0
🚚 [consumer-single] 处理订单: STREAM_ORDER_1
✅ [consumer-single] 订单[STREAM_ORDER_1] ACK完成
🚚 [consumer-single] 处理订单: STREAM_ORDER_2
✅ [consumer-single] 订单[STREAM_ORDER_2] ACK完成
🚚 [consumer-single] 处理订单: STREAM_ORDER_3
✅ [consumer-single] 订单[STREAM_ORDER_3] ACK完成
🚚 [consumer-single] 处理订单: STREAM_ORDER_1
✅ [consumer-single] 订单[STREAM_ORDER_1] ACK完成
🚚 [consumer-single] 处理订单: STREAM_ORDER_2
✅ [consumer-single] 订单[STREAM_ORDER_2] ACK完成
🚚 [consumer-single] 处理订单: STREAM_ORDER_3
✅ [consumer-single] 订单[STREAM_ORDER_3] ACK完成
[consumer-single] 消费异常
5.5. Stream消费者组:多实例负载均衡实战
💡 场景:3个订单处理服务实例同时消费,Redis自动分配消息
5.5.1. 服务层(扩展自上一示例)
java
@Service
@Slf4j
public class MultiConsumerStreamService {
private static final String ORDER_STREAM = "stream:order:multi";
private static final String CONSUMER_GROUP = "order-multi-group";
@Autowired
private RedisStreamService streamService;
@Autowired
private RedisTemplate<String, Object> streamRedisTemplate; // 注入RedisTemplate
private final StreamOperations<String, Object, Object> streamOperations;
public MultiConsumerStreamService(RedisTemplate<String, Object> streamRedisTemplate) {
this.streamRedisTemplate = streamRedisTemplate;
this.streamOperations = streamRedisTemplate.opsForStream();
}
@PostConstruct
public void init() {
streamService.xGroupCreate(ORDER_STREAM, CONSUMER_GROUP);
}
/**
* 启动指定名称的消费者
*/
public void startConsumer(String consumerName) {
new Thread(() -> {
log.info("✅ 启动消费者: {}", consumerName);
processOrders(consumerName); // 使用自己的处理方法
}, consumerName).start();
}
/**
* 处理订单(多消费者版本)
*/
private void processOrders(String consumerName) {
while (true) {
try {
// 配置读取选项:每次最多10条,阻塞5秒
StreamReadOptions options = StreamReadOptions.empty()
.count(10)
.block(Duration.ofSeconds(5));
// 从>位置读取新消息(不读取pending消息)
StreamOffset<String> offset = StreamOffset.create(ORDER_STREAM, ReadOffset.lastConsumed());
List<MapRecord<String, Object, Object>> records =
streamOperations.read(Consumer.from(CONSUMER_GROUP, consumerName), options, offset);
if (records != null && !records.isEmpty()) {
for (MapRecord<String, Object, Object> record : records) {
try {
Map<Object, Object> data = record.getValue();
String orderId = (String) data.get("orderId");
log.info("🚚 [{}] 处理订单: {}", consumerName, orderId);
// 模拟业务处理
Thread.sleep(150);
// 手动ACK
streamOperations.acknowledge(ORDER_STREAM, CONSUMER_GROUP, record.getId().toString());
log.info("✅ [{}] 订单[{}] ACK完成", consumerName, orderId);
} catch (Exception e) {
log.error("[{}] 处理订单失败: {}", consumerName, record.getId(), e);
}
}
} else {
log.debug("[{}] 未获取到新消息,继续等待...", consumerName);
}
} catch (Exception e) {
log.error("[{}] 消费异常", consumerName, e);
try {
Thread.sleep(2000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
}
/**
* 添加测试订单
*/
public void addTestOrders(int count) {
for (int i = 1; i <= count; i++) {
Map<String, Object> order = new HashMap<>();
order.put("orderId", "MULTI_" + i);
order.put("amount", i * 100);
streamService.xAdd(ORDER_STREAM, order);
}
log.info("📦 已添加{}个测试订单", count);
}
}
5.5.2. 测试类(模拟3个实例)
java
@SpringBootTest
@Slf4j
class MultiConsumerStreamServiceTest {
@Autowired
private MultiConsumerStreamService multiConsumerService;
@Test
void testMultiConsumer() throws InterruptedException {
// 启动3个消费者实例
multiConsumerService.startConsumer("consumer-A");
multiConsumerService.startConsumer("consumer-B");
multiConsumerService.startConsumer("consumer-C");
// 添加10个订单
multiConsumerService.addTestOrders(10);
// 等待处理
Thread.sleep(10000);
// 【执行结果预留位】
// 2026-02-01 15:15:05.111 INFO ✅ 启动消费者: consumer-A
// 2026-02-01 15:15:05.113 INFO ✅ 启动消费者: consumer-B
// 2026-02-01 15:15:05.115 INFO ✅ 启动消费者: consumer-C
// 2026-02-01 15:15:05.210 INFO 📦 已添加10个测试订单
// 2026-02-01 15:15:10.310 INFO 🚚 [consumer-A] 处理订单: MULTI_1
// 2026-02-01 15:15:10.315 INFO 🚚 [consumer-B] 处理订单: MULTI_2
// 2026-02-01 15:15:10.320 INFO 🚚 [consumer-C] 处理订单: MULTI_3
// 2026-02-01 15:15:10.470 INFO ✅ [consumer-A] 订单[MULTI_1] ACK完成
// ...(负载均衡效果:3个消费者分担10个订单)
// 2026-02-01 15:15:12.105 DEBUG [consumer-B] 未获取到新消息,继续等待...
}
}
5.5.3. 执行结果
启动消费者: consumer-A
启动消费者: consumer-B
启动消费者: consumer-C
[consumer-B] 处理订单: MULTI_1
[consumer-C] 处理订单: MULTI_1
[consumer-A] 处理订单: MULTI_1
[consumer-A] 订单[MULTI_1] ACK完成
[consumer-B] 订单[MULTI_1] ACK完成
[consumer-A] 订单[MULTI_7] ACK完成
...
[consumer-A] 处理订单: MULTI_10
[consumer-B] 订单[MULTI_10] ACK完成
[consumer-A] 订单[MULTI_10] ACK完成
6. Spring Boot集成关键总结
| 场景 | 推荐方案 | 核心配置 | 注意事项 |
|---|---|---|---|
| 简单队列 | List + BRPOP | @Async + 阻塞弹出 |
消息丢失风险(需业务补偿) |
| 延时任务 | Sorted-Set | @Scheduled定时扫描 |
时间精度到秒级 |
| 实时通知 | PUB/SUB | RedisMessageListenerContainer |
消息不持久化,适合非关键通知 |
| 高可靠队列 | Stream + 消费者组 | autoAcknowledge=false + 手动ACK |
生产环境首选,支持消息重试 |
| 多实例负载 | Stream消费者组 | 多实例共享消费者组 | Redis自动分配消息,避免重复消费 |
✨ 黄金法则 :
1️⃣ 关键业务 (如订单)→ 用Stream + 消费者组 + 手动ACK
2️⃣ 非关键通知 (如日志)→ 用PUB/SUB
3️⃣ 延时场景 → Sorted-Set + 定时扫描
4️⃣ 简单场景 → List(注意:无ACK机制!)
7. Redis队列的性能对比与最佳实践
7.1性能对比(Redis 7.4.7测试)
| 队列类型 | 1000次操作耗时(ms) | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| List | 12.5 | 简单队列 | 实现简单,性能好 | 无消息确认,不能重复消费 |
| Sorted-Set | 15.2 | 延时队列 | 支持延时,按时间排序 | 不能重复消息,性能稍差 |
| PUB/SUB | 9.8 | 通知、广播 | 实时性强,支持多消费者 | 消息可能丢失,无持久化 |
| Stream | 8.5 | 高性能队列 | 支持持久化、消费者组 | 学习曲线较陡 |
💡 最佳实践:
- 简单队列:使用List,适合订单处理、日志记录等场景
- 延时队列:使用Sorted-Set,适合定时任务、优惠券过期等场景
- 通知广播:使用PUB/SUB,适合用户通知、系统消息等场景
- 高性能队列:使用Stream,适合电商秒杀、高并发消息处理等场景
8. 往期回顾
【Java线程安全实战】⑬ volatile的奥秘:从"共享冰箱"到内存可见性的终极解析
【Java线程安全实战】⑭ ForkJoinPool深度剖析:分治算法的"智能厨房"如何让并行计算跑得更快
【后端】【工具】Redis Lua脚本漏洞深度解析:从CVE-2022-0543到Redis 7.x的全面防御指南
【后端】【Redis】① Redis8向量新特性:从零开始构建你的智能搜索系统
【后端】【Redis】② Redis事务管理全解:从"购物车结算"到"银行转账",一文彻底掌握事务机制
9. 经典书推荐
《Redis设计与实现》(第2版)
作者:黄健宏
为什么推荐:从源码层面讲解Redis核心机制(包括队列),有大量示意图和代码注释。2023年最新版,覆盖Redis 7.0+,适合从应用层到源码层的深度学习。
📚 摘录:"Redis的队列机制是为了解决异步消息处理的问题,它的设计哲学是'简单优于复杂',所以没有实现复杂的重试机制,而是依赖应用层处理。"
10. 参考资料
Redis官方文档 - Stream
Redis队列详解(springboot实战)
11. 代码示例已开源,欢迎下载使用!
为了方便大家快速上手和验证文中所有 Redis 队列实战示例(包括 List、Sorted Set、Pub/Sub、Stream 单消费者与多实例负载均衡等完整 Spring Boot 工程),全部可运行代码已整理并上传至 Gitee 开源仓库。
✅ 特点说明:
- 基于 Spring Boot 2.6 + JDK 8 + Redis 8 开发
- 包含完整的
application.yml配置与测试类 - 无需积分、无需关注、无需转发,免费直接下载
- 项目结构清晰,开箱即用,适合学习与生产参考
🔗 直接点击附件下载
(请将链接中的 yourname 替换为您的实际仓库路径)
💬 如果您在使用过程中有任何疑问,或希望增加更多场景(如消息重试、死信队列、监控告警等),欢迎在仓库 Issues 中留言,我会持续更新和完善!
觉得有帮助?别忘了点个 ⭐ Star 支持一下! 😊