Lettuce连接未释放的典型表现是应用退出后redis-cli client list仍显示大量idle=0或idle异常大的连接,且lettuce-nioEventLoop线程持续RUNNING;根本原因是Spring Boot默认不主动触发Lettuce资源清理,需显式配置shutdownTimeout>0并禁用自动重连。

Spring Boot shutdown 时 Lettuce 连接未释放的典型表现
应用进程退出后,redis-cli client list 仍能看到大量 idle=0 或 idle 值异常大的连接,甚至出现 client long down 报警;日志里看不到 LettuceConnectionFactory.destroy() 被调用的痕迹;用 jstack 查看线程,发现 lettuce-nioEventLoop 线程仍在 RUNNING 状态。这不是“连接泄漏”的错觉,而是 Spring Boot 默认不主动触发 Lettuce 资源清理——尤其当应用被 kill -9 或容器强制终止时,destroy() 根本没机会执行。
必须显式配置 Lettuce 的 shutdown-timeout 和 close-on-shutdown
Spring Boot 3.x 的 LettuceClientConfigurationBuilderCustomizer 是唯一可靠入口。仅靠 spring.redis.lettuce.pool.shutdown-timeout 不起作用,它只影响连接池,不控制底层 Netty EventLoop 和连接本身。
- 在配置类中注册自定义 builder:
@Bean
public LettuceClientConfigurationBuilderCustomizer customizer() {
return builder -> builder.shutdownTimeout(Duration.ofSeconds(5))
.commandTimeout(Duration.ofSeconds(2))
.clientOptions(ClientOptions.builder()
.autoReconnect(true)
.pingBeforeActivateConnection(true)
.build());
}
-
shutdownTimeout必须设为 >0(默认是 0,即跳过优雅关闭) -
autoReconnect=true防止 shutdown 过程中因重连尝试卡住 - 不要依赖
@PreDestroy或DisposableBean.destroy()—— 它们在 JVM 快速退出时不可靠
容器环境需额外处理 SIGTERM 与 JVM 关闭钩子冲突
Kubernetes 或 Docker 默认发 SIGTERM 给主进程,但 JVM 的 Runtime.addShutdownHook 可能被 Netty 的 NIO 线程阻塞,导致超时后容器强行 SIGKILL。此时 Lettuce 连接必然残留。
- 在
Dockerfile中启用init进程(如tini),避免信号被子进程吞掉 - 设置
terminationGracePeriodSeconds: 30,给 shutdown 流程留出缓冲时间 - 禁用 Lettuce 的
disconnectedBehavior自动重连逻辑(默认是RECONNECT),否则 shutdown 阶段可能触发新连接 - 检查是否启用了
spring.redis.lettuce.shutdown-timeout,该配置项在 Spring Boot 3.2+ 中已被废弃,必须走 builder 方式
验证连接是否真关闭:别只信日志
日志里出现 Shutting down RedisConnectionFactory 并不等于连接已断开。真正有效的验证方式只有两种:
- 在应用停止前、后分别执行:
redis-cli -h $REDIS_HOST client list | wc -l,对比数量变化 - 抓包验证:
tcpdump -i any port 6379 and host $REDIS_HOST,确认 shutdown 后无 FIN/ACK 流量 - 如果使用云 Redis(如阿里云 Tair、腾讯云 CRS),务必查控制台的“客户端连接数”曲线,它比本地
client list更真实——因为代理层会聚合连接
最常被忽略的一点:Lettuce 的 shutdownTimeout 是指整个资源释放流程的上限,不是单个连接的关闭耗时。若设得太短(比如 100ms),Netty EventLoop 可能直接被中断,连接反而卡在 CLOSE_WAIT 状态更久。


















