spring boot 3.x 默认不自动关闭 redis 连接池,必须显式配置销毁逻辑,否则连接残留导致 close_wait 堆积、maxclients 超限或资源泄漏;@predestroy 失效主因是连接工厂未被 spring 容器正确管理或自动配置未触发,可靠方案是 @bean(destroymethod = "destroy") 显式声明并配合 lettuceclientconfiguration.shutdowntimeout 及全局优雅停机配置。

Spring Boot 3.x 默认不会自动关闭 Redis 连接池,必须显式配置销毁逻辑,否则 RedisConnectionFactory 持有的连接会残留,导致 CLOSE_WAIT 连接堆积、maxclients 超限或锁资源泄漏。
为什么 @PreDestroy 在 RedisConnectionFactory 上不生效?
Spring Boot 3 默认使用 LettuceConnectionFactory(或 JedisConnectionFactory),它本身是 DisposableBean,但其销毁方法 destroy() 不会自动被调用——除非该 Bean 被 Spring 容器管理且生命周期明确绑定到上下文关闭流程。常见失效场景:
- 手动 new 出的连接工厂(绕过 Spring 管理)
- 配置类中未用
@Bean声明,而是直接返回实例 - 使用了
@Primary或条件化@ConditionalOnMissingBean,但销毁顺序被其他 Bean 干扰 -
spring.redis.url配置缺失或错误时,自动配置未触发,RedisAutoConfiguration未加载,自然无销毁链路
正确配置 @Bean + @PreDestroy 的最小可行写法
不要依赖自动配置“默认就关”,必须显式声明并确保销毁回调注册成功。以下是最简可靠模式:
@Configuration
public class RedisConfig {
@Bean(destroyMethod = "destroy")
public RedisConnectionFactory redisConnectionFactory(RedisProperties properties) {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.shutdownTimeout(Duration.ofSeconds(2))
.build();
RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration(
properties.getHost(), properties.getPort());
standaloneConfig.setPassword(RedisPassword.of(properties.getPassword()));
return new LettuceConnectionFactory(standaloneConfig, clientConfig);
}
}
关键点:
-
destroyMethod = "destroy"显式声明销毁方法,比@PreDestroy更底层、更可靠 -
LettuceClientConfiguration.builder().shutdownTimeout(...)控制底层 Netty 连接关闭等待时间,避免阻塞整个 shutdown phase - 不要在
redisConnectionFactory方法里捕获异常并静默返回 null —— 这会导致 Spring 认为 Bean 创建失败,后续RedisTemplate注入为空,且无销毁机会
配合 Spring Boot 3 优雅停机全局超时设置
即使 RedisConnectionFactory 销毁逻辑写对了,若容器级 shutdown phase 超时太短,也会被强制中断。Spring Boot 3 默认禁用优雅停机,必须主动开启:
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 20s
注意:
-
timeout-per-shutdown-phase是所有 Bean 销毁阶段的总宽限期(包括线程池、MQ、Redis 等),不是单个 Bean 的时间 - Lettuce 的
shutdownTimeout应小于该值(如设为 2s),否则连接池关闭可能拖满整个 phase,触发强制终止 - 若应用还用了
RedissonClient,它不参与 Spring 的destroyMethod流程,必须单独调用redissonClient.shutdown(),建议在ContextClosedEvent监听器中执行
验证是否真正关闭:三个必查信号
光看日志“xxx destroyed”不等于连接已释放。上线前务必检查:
- 执行
redis-cli CLIENT LIST | grep -c "addr=",停机后应为 0(排除 pubsub client 残留) - Linux 下执行
netstat -an | grep :6379 | grep CLOSE_WAIT | wc -l,应稳定为 0 - JVM shutdown hook 中打印
RedisConnectionFactory的getPoolSize()(Lettuce 无直接 API,可反射查pool字段),确认连接数归零
最容易被忽略的是:Lettuce 默认启用连接池复用和空闲检测,它的“关闭”不是瞬间断连,而是触发异步清理;若没配 shutdownTimeout 或超时太短,Netty Channel 就卡在半关闭状态。











