Lettuce 的 StatefulRedisPubSubConnection 默认不自动重连,需显式配置 ReconnectStrategy 并监听连接状态变化;非线程安全,须单线程管理订阅;onMessage() 不可阻塞,应避免耗时操作;需复用 RedisClient 和 ClientResources 防连接泄漏;消息丢失需靠 Redis Streams 或本地队列兜底。

订阅连接断开后不会自动重连
Lettuce 的 StatefulRedisPubSubConnection 默认不启用自动重连,一旦网络抖动或 Redis 重启,connection.isOpen() 可能仍返回 true,但后续 publish 或新 subscribe 会静默失败或抛 RedisCommandTimeoutException。
必须显式配置重连策略并监听状态变化:
- 创建连接时传入
ClientResources.builder().reconnectStrategy(...),推荐用ReconnectStrategy.exponential(100, 30_000)(从 100ms 开始指数退避,上限 30s) - 注册
connection.addListener(new RedisPubSubAdapter() { ... }),在onDisconnect()和onConnect()里清理旧 channel、重新subscribe - 避免在
onMessage()里做耗时操作——它运行在 Netty EventLoop 线程,阻塞会导致心跳超时、连接被误判为断开
多个线程调用 subscribe/unsubscribe 不安全
StatefulRedisPubSubConnection 不是线程安全的,多线程并发调用 sync().subscribe(...) 可能触发 IllegalStateException: Connection is not active,尤其在重连过程中。
正确做法是统一由一个线程管理订阅状态:
- 用单线程
EventLoopGroup(如DefaultEventLoopGroup(1))绑定连接,确保所有subscribe/unsubscribe调用串行化 - 业务线程通过
channel.writeAndFlush()或队列把订阅请求投递给该线程,而非直接调用sync() - 如果必须动态增减订阅,优先用
async().subscribe(...)+addListener()处理回调,避免阻塞
消费延迟高或消息丢失的典型原因
看似连接正常,但消息积压或漏收,往往不是 Redis 问题,而是客户端侧缓冲和线程模型没对齐:
-
EventLoopGroup线程数过少(默认为 CPU 核数),而onMessage()里做了 DB 查询或 HTTP 调用,导致事件循环卡住,新消息无法及时分发 - 未设置
clientOptions().publishOnScheduler(true),导致onMessage()在 Netty I/O 线程执行,加剧阻塞风险 - Redis 服务端配置了
timeout 0,但 Lettuce 心跳默认 60s,中间网络闪断可能来不及探测;建议显式设clientOptions().pingBeforeActivateConnection(true)并缩短keepAlive间隔
连接泄漏导致 OOM 或 Too Many Open Files
每次新建 RedisClient + connectPubSub() 都会创建独立的 EventLoopGroup 和 TCP 连接,忘记 close() 就会累积句柄。
可靠模式只应存在一个全局 RedisClient 实例:
- 用单例持有
RedisClient和复用的ClientResources(含共享EventLoopGroup) -
StatefulRedisPubSubConnection可以多次connectPubSub()复用,但每个连接需显式close();若长期运行,更推荐复用同一连接实例 - 检查
lsof -p $PID | grep redis,确认活跃连接数是否随时间增长——这是泄漏最直接的信号
真正难处理的是重连期间的状态同步:Redis 订阅关系不持久,断连后需重新 subscribe,但中间发布的消息完全丢失;如果业务不能容忍丢消息,Lettuce 本身不解决这个问题,得换成 Redis Streams + consumer group 或加一层本地消息队列兜底。










