根本原因是应用侧频繁创建短连接且连接池未生效:手动new连接、未指定connectionFactory、异步任务绕过池、测试中直接connect/close等,导致客户端主动关闭后大量TIME_WAIT堆积,耗尽本地端口。
Spring Boot集成Redis后出现大量TIME_WAIT连接,根本不是Redis服务端的问题,而是应用侧频繁创建短连接、连接池未生效或被绕过导致的。Linux内核在客户端主动关闭TCP连接后强制维持TIME_WAIT(通常60秒),端口无法复用,QPS稍高就可能耗尽本地端口范围(32768–65535)。
Spring Boot默认使用Lettuce(2.0+)或Jedis(旧版),但很多场景下连接池实际未被复用:
RedisConnectionFactory或重复调用getRedisConnection(),绕过了Spring管理的连接池@Bean定义RedisTemplate时未指定connectionFactory,或工厂本身是每次new出来的实例@Async)中直接new RedisClient或StatefulRedisConnection,未从连接池获取new RedisClient(...) + connect() + close(),等同于每调用一次建一个TCP连接别查Redis服务器,要在Spring Boot应用所在宿主机执行:
ss -tan state time-wait sport = :6379 | wc -l
再按源IP聚合看是否集中于本机:
ss -tan sport = :6379 | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head -5
若输出第一列为本机IP且数值持续 > 2000,基本可锁定是该应用发起的短连接风暴;若为其他IP,则问题不在本服务。
Spring Boot 2.3+ 默认启用Lettuce,但maxConnections、minIdle等参数必须显式配置才真正起作用:
spring.redis.lettuce.pool.max-active=200:设为预估并发峰值的1.5倍(如QPS 100 → 设150),太小会导致阻塞,太大则内存浪费spring.redis.lettuce.pool.time-between-eviction-runs=30000:必须开启空闲连接清理,否则连接长期滞留不释放spring.redis.lettuce.shutdown-timeout=0:避免容器停机时强制中断连接,引发非优雅关闭connection.close()或redisClient.shutdown()——Lettuce连接由池管理,手动关会破坏复用逻辑Jedis默认是同步阻塞I/O,且JedisPool配置稍有不慎就会退化成短连接:
maxTotal=1或blockWhenExhausted=false → 池满时直接抛异常,业务层常跟着new新连接补救testOnBorrow=true且minEvictableIdleTimeMillis过长 → 大量失效连接堆积在池中,业务取到后立即报错并新建连接spring-boot-starter-data-redis的默认依赖,可能意外引入Jedis而没意识到最易被忽略的是:即使配置了连接池,只要某处代码写了new Jedis(...)或redisClient.connect()并立即close(),那一行就足以让整个池形同虚设——TIME_WAIT不会因“用了Spring”自动消失,只取决于你实际怎么发TCP包。