Redis连接池耗尽需防客户端“假活”,启用tcp-keepalive可探测并断开僵尸连接;服务端配置tcp-keepalive 60等参数,客户端同步设置setMaxWaitMillis(2000)和setBlockWhenExhausted(false),并确保jedis.close()正确调用。

Redis 连接池耗尽,单纯调大 maxTotal 很可能治标不治本;真正要防的是客户端连接“假活”——线程卡住、没释放、也没断开,但连接一直占着不还,最终把池子堵死。
为什么 tcp-keepalive 能切中要害
客户端异常崩溃、网络闪断、GC 停顿过长、代码里 jedis.close() 被跳过(比如没进 finally)……这些都会导致 Redis 服务端还维着 TCP 连接,而客户端早已不响应。Jedis 连接池不会主动感知这种“僵尸连接”,只会等它超时或被强制回收。服务端启用 tcp-keepalive 后,内核会周期性发探测包,收不到 ACK 就断连,空出连接槽位。
这不是替代连接池配置,而是补上客户端不可靠时的最后一道防线。
-
tcp-keepalive是 Linux 内核行为,对 Redis 进程透明,无需改客户端代码 - 默认关闭,必须显式开启;且仅作用于服务端监听的 socket,不影响客户端本地设置
- 生效前提是客户端 TCP 栈未禁用 keepalive(Java 默认开启,一般不用动)
怎么在 Redis 服务端配 tcp-keepalive
修改 redis.conf,加这三行(单位全是秒):
tcp-keepalive 60 # 每隔 60 秒发一次探测包 <p>tcp-keepidle 60</p><h1>连接空闲满 60 秒后才开始探测(部分老版本只认 tcp-keepalive)</h1><p>tcp-keepintvl 10</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill11046" title="Sentinal Redis"><img src="https://img.php.cn/upload/skill/001/246/273/6adaf20dbae7c507.jpg" alt="Sentinal Redis" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill11046" title="Sentinal Redis" class="overflowclass">Sentinal Redis</a> <p class="overflowclass">监控Redis服务器健康状况、内存、性能及BullMQ队列。检查队列深度、失败任务,分析慢查询并诊断问题。</p> </div> <a rel="nofollow" href="/xiazai/skill11046" title="Sentinal Redis" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div><h1>探测失败后,每 10 秒重试一次(部分系统需额外设 tcp-keepcnt)</h1>
注意:tcp-keepalive 是 Redis 3.2.1+ 的配置项,低版本只支持 timeout(它只关空闲连接,不处理卡死连接);若你用的是 Redis 6+,建议同时保留 timeout 60 作为兜底。
- 别设太小(如
tcp-keepalive 5),高频探测可能加重内核负担,尤其连接数多时 - 别设太大(如
tcp-keepalive 600),僵尸连接挂太久,池子照样被占满 - 改完记得
redis-cli CONFIG REWRITE或重启,CONFIG GET tcp-keepalive可验证
JedisPool 配置必须同步收紧
服务端加了保活,客户端不能还傻等。否则线程还在 borrowObject() 里阻塞,池子早空了,新请求全卡住。
- 务必设
setMaxWaitMillis(2000):超过 2 秒拿不到连接就抛异常,别死等 - 设
setBlockWhenExhausted(false):池子空了直接报错,而不是让线程排队等(避免 Dubbo 线程池被拖垮) - 禁用
testOnBorrow(除非你真需要):每次取连接都 ping 一次,延迟叠加明显;靠tcp-keepalive+ 合理timeout更轻量 - 所有
jedis实例必须用 try-with-resources 或finally { jedis.close() },这是底线
怎么确认问题真被解决了
别只看日志有没有 Could not get a resource from the pool,那只是结果。要盯两个指标:
- 执行
redis-cli info clients,重点关注connected_clients和client_recent_max_input_buffer—— 如果前者长期接近maxclients,后者又持续偏高,说明有连接卡在读缓冲区没清空 - 用
redis-cli client list抓几个连接,看idle字段:如果大量连接idle超过你设的tcp-keepalive值却没断开,说明 keepalive 没生效(可能是内核参数被覆盖,或中间有代理劫持了 TCP)
最麻烦的情况是:服务端 keepalive 断了连接,但客户端线程还没从 borrowObject() 返回,仍在等待——这时得靠 setMaxWaitMillis 和 setBlockWhenExhausted(false) 把它拽出来,否则监控看不出异样,但请求就是慢。










