不能直接用RedisTemplate做非阻塞读取,因其封装的是同步API,rightPop、blPop等方法具有同步阻塞语义;必须使用ReactiveRedisTemplate,它返回Mono/Flux,全程响应式。

为什么不能直接用 RedisTemplate 做非阻塞读取
RedisTemplate 底层默认使用 Lettuce,但它封装的是同步 API。即使连接是线程安全的、底层 I/O 是非阻塞的,rightPop、blPop 这类方法调用仍是**同步阻塞语义**——线程会挂起等待结果,不符合 Reactive 编程中“不占线程、可组合、支持背压”的要求。
真正实现非阻塞读取,必须切换到 ReactiveRedisTemplate,它返回的是 Mono 或 Flux,整个链路从连接、命令执行到结果消费都是响应式的。
引入 reactive 依赖并确认客户端是 Lettuce
Spring Boot 3.x 默认已弃用 Jedis,且 spring-boot-starter-data-redis-reactive 强制使用 Lettuce(基于 Netty),这是前提。
- 确保
pom.xml中只引入了spring-boot-starter-data-redis-reactive,不要混用spring-boot-starter-data-redis(否则可能触发自动配置冲突) - 检查
application.yml中未显式指定spring.redis.client-type=jedis(这会导致 reactive 模块初始化失败) - Lettuce 的连接工厂
LettuceConnectionFactory必须启用响应式支持:默认开启,但若自定义了连接工厂,需确认构造时未禁用useSsl或usePooling等影响 reactor 兼容性的选项
配置 ReactiveRedisTemplate 并使用 blPop 实现带超时的非阻塞队列消费
ReactiveRedisTemplate 不提供 rightPop 这种纯阻塞变体,但提供了 blPop 的响应式版本 —— 它本质是 Redis 的阻塞命令,但在 Reactive 层被包装为非阻塞调用:线程不卡死,而是注册回调、交由 Netty EventLoop 调度。
示例:监听队列 queue:task,最多等 5 秒
reactiveRedisTemplate.opsForList()
.blPop(Duration.ofSeconds(5), "queue:task")
.map(tuple -> tuple.getValue()) // tuple.getSecond() 是实际弹出的值
.onErrorResume(e -> {
// 超时或连接异常时返回空 Mono,避免中断流
if (e instanceof RedisSystemException && e.getMessage().contains("timeout")) {
return Mono.empty();
}
return Mono.error(e);
});
-
blPop返回Mono,其中> tuple.getSecond()才是你要的数据 - 超时不是抛异常,而是返回空
Mono;但网络断连、序列化失败等仍会抛RedisSystemException,需按需捕获 - 别用
lPop(非阻塞版)——它立即返回null或值,无法等待,不适合消费者轮询场景
消费者如何持续运行而不泄漏资源
用 Flux.interval + flatMap 拉取容易造成无节制并发或连接堆积;正确做法是用 concatMap 串行消费,或用 handle + 循环递归保持单订阅流。
- 推荐模式:用
Flux.generate或递归expand构建长生命周期流,每次消费完再触发下一次blPop,天然限流 - 务必在
@Bean中声明为DisposableBean或监听ContextClosedEvent,显式调用reactiveRedisTemplate.getConnectionFactory().destroy(),否则 Netty 连接池不会释放 - 如果业务处理耗时较长(如调用外部 API),记得在
flatMap内用publishOn(Schedulers.boundedElastic())切换线程,避免阻塞 Netty EventLoop
最易被忽略的一点:ReactiveRedisTemplate 的序列化器默认是 JdkSerializationRedisSerializer,它不兼容跨语言或前端 JSON 格式。生产环境必须显式配置 GenericJackson2JsonRedisSerializer,否则 blPop 取出的对象反序列化会失败或类型错乱。











