DEBUG.WIKI
丽萨主机;为跨境业务选好 IP;原生 / 住宅 IP · 按套餐选择;查看 IP →
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
OnlineSIM:在线接收短信,虚拟号码租用

Jedis close 报 Could not return the broken resource,连接池正在补连接

Redis 已断开,关闭坏连接时又出现连接超时,检查 commons-pool 的替补连接路径。2.13.1 会在失效后尝试补充对象;保留原业务异常,并处理清理阶段的第二个错误。

Redis 连接断开后,业务已经捕获了连接错误,执行 jedis.close() 时却又抛出:

redis.clients.jedis.exceptions.JedisException:
Could not return the broken resource to the pool

继续往下看,原因竟然是 Failed to connect、Connection refused 或连接超时。关闭旧连接为什么在建立新连接?这时要查看 Pool.returnBrokenResource → GenericObjectPool.invalidateObject → addObject 这段调用链。

坏连接被移除后,池尝试补一个新的

Jedis 发现连接已损坏,会把它交给池做失效处理。commons-pool 2.13.1 的 invalidateObject() 在销毁旧对象后,只要池尚未关闭,就尝试补充新对象。Redis 仍不可达,补建连接失败,异常再被 Jedis 包装成“无法归还坏资源”,从 close 返回。

因此,错误不一定说明坏连接还被留在池里,也不是应用的 close 凭空变成了一次业务查询。要分开看旧对象销毁与替补对象创建,保存完整原因链,不能只凭最外层文字判断归还状态。

具体故障包括 Jedis 7.5.0 和 8.0.0,但依赖边界很关键:7.x 声明的 commons-pool 版本与 8.0.0 不同,其他依赖或平台管理可以让运行时解析成 2.13.x。不要只看 Jedis 主版本就推断池实现。

先保留业务错误,再处理关闭错误

使用 try-with-resources 时,若业务体已经抛出异常,Java 会把关闭时的异常加入原异常的 suppressed 列表。下面是假设 pool 已由应用管理的局部调用:

try (Jedis jedis = pool.getResource()) {
    jedis.ping();
}

日志需要保存主异常、cause 及 suppressed 信息。若业务代码自己先捕获并吞掉了 ping 的异常,close 抛出的异常就可能成为最后唯一可见的错误;把它误认成首次故障,会查错方向。

try-with-resources 不会让 close 自动变成永不失败。业务正常结束而关闭失败时,仍需报告关闭错误;手写 finally 的项目也要避免清理异常覆盖已经发生的业务异常。不要为了让日志干净就不关闭 Jedis,或捕获所有 RuntimeException 后继续把同一对象当作可用连接。

依赖回退不能当作无条件修复

commons-pool 2.12.1 在没有等待借用者时,补充行为与 2.13.1 不同,但存在等待借用者时也可能从这条路径抛出异常。因此“锁回 2.12.1 就能保证 close 不抛错”并不成立,还可能改变其他池行为。

Jedis 8.0.2 的相关 Pool 包装仍会传播失效处理异常。截至 2026 年 10 月 24 日,不能把上游拟议的补充策略调整当成已部署正式版本的保证。需要升级或锁版本时,核对实际解析的 commons-pool、Jedis 及平台约束,在隔离环境覆盖 Redis 不可达和池中有人等待两种情况。

服务退出时按已有顺序停止接收任务、等待借用者结束、关闭池,避免在正常停机中不断补建连接。突发 Redis 故障则仍需按业务的降级和重试策略处理;清理报错本身不证明上一次写命令没有执行,不能因此自动重发非幂等写入。

复查时确认坏连接没有再次交给业务、主异常仍能看见,Redis 恢复后池能按预算重新提供连接。保留原连接池配置与依赖锁定结果,便于回退;不要通过停掉生产 Redis 来临时制造这类测试。