rtopic不支持自动重连与消息持久化,断网后监听器静默失效,因其依赖redis原生无状态pub/sub;需手动实现连接监控、重订阅及缓冲,或改用rstream保障可靠性。

Redisson 的发布订阅不是开箱即用的“自动重连+消息不丢”方案,它默认不保证离线期间的消息可达,也不自动重订阅;想靠 RTopic 实现可靠通知,必须手动补足重连、重订阅、本地缓冲等逻辑。
为什么 RTopic.addListener() 在断网后收不到新消息
Redisson 的 RTopic 订阅基于底层 Redis Pub/Sub 连接,该连接是短生命周期的:一旦网络中断或 Redis 重启,连接断开,RTopic 不会自动重建订阅关系,也不会回溯历史消息(Redis Pub/Sub 本身无消息持久化)。
- 现象:
addListener()注册的监听器在 Redis 临时不可达后彻底静默,无异常抛出,也无回调触发 - 根本原因:Redisson 默认使用
SingleServerConfig或ClusterServersConfig中的subscriptionConnectionPoolSize和subscriptionConnectionMinimumIdleSize管理订阅连接池,但不监控连接有效性,也不触发自动重订阅 - 解决方向:必须监听
RedissonClient的连接状态,并在连接恢复后主动调用RTopic.removeListener()+RTopic.addListener()重建监听
如何让 RTopic 支持断线重连后的自动重订阅
Redisson 没有内置“auto-resubscribe”开关,需结合 RedisPubSubListener 和客户端生命周期事件手动实现:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 用
redisson.getCodec().getMapCacheCodec()或自定义 codec 确保序列化兼容(尤其跨服务版本时) - 监听
RedissonClient的addListener(RedisNodeNotFoundException.class, ...)和addListener(ConnectionWatchdog.class, ...)类事件(需反射获取或依赖Redisson内部事件机制) - 更稳妥做法:启动时启动一个后台线程,定期调用
redisson.getNodesGroup().pingAll(),失败时清空本地RTopic缓存并重新getTopic(...).addListener(...) - 示例关键片段:
RTopic topic = redisson.getTopic("alarm:channel"); topic.addListener(String.class, (channel, msg) -> System.out.println("received: " + msg)); // 后续需自行维护该 topic 引用,并在网络恢复后重新 addListener
RTopic 和 RStream 到底该选哪个
如果业务要求“至少一次投递”或需要消费组、消息确认、未读消息回溯,RTopic 从设计上就不适用——它只是对 Redis 原生 Pub/Sub 的封装;此时应直接切到 RStream(Redis 5.0+ Stream 数据结构 + Redisson 封装):
-
RTopic:适合实时性高、可容忍丢失的场景,如服务心跳广播、配置刷新通知 -
RStream:支持消费者组(RStream.readGroup())、pending list、ACK 机制;消息写入即持久化,断线重连后可拉取未 ACK 消息 - 注意:
RStream需 Redis 服务端开启 AOF 或 RDB 持久化,否则重启后 stream 数据丢失;而RTopic即使开了 AOF 也无法持久化 Pub/Sub 消息 - 迁移成本:改用
RStream需调整生产者调用rstream.add(),消费者改用readGroup()+acknowledge(),无法与原有RTopic兼容
监听器里做耗时操作会导致什么
RTopic 的监听器默认运行在 Netty EventLoop 线程中,若在回调里执行数据库查询、HTTP 调用或锁等待,会阻塞整个订阅连接的事件处理,轻则延迟接收新消息,重则触发连接超时断开:
- 典型错误:
topic.addListener(..., (c,m) -> userDao.saveLog(m))—— 直接在监听线程里写 DB - 正确做法:用
CompletableFuture.runAsync(() -> {...}, executor)拆出异步线程池,或改用RTopic.addListenerAsync()(仅 Redisson 3.23.0+ 支持,且仍需注意线程上下文传递) - 务必限制自定义线程池大小(如
new ThreadPoolExecutor(4, 8, 60, TimeUnit.SECONDS, new SynchronousQueue())),避免突发流量打爆 JVM
真正难的不是写通 RTopic,而是判断它是否真的适合你的场景——很多团队踩坑后才发现,所谓“实时通知”,其实隐含了“不能丢”“要有序”“得可追溯”等需求,而这些恰恰是 Redis Pub/Sub 天然不提供的。别被 addListener 的简洁语法骗了,先想清楚语义,再选 API。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










