redis 连接池耗尽,经常导致redis客户端连接数波动异常问题解决
1.原配置
修改之前 <!-- 连接池配置 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!-- 最大连接数 --> <property name="maxTotal" value="200" /> <!-- 最大空闲连接数 --> <property name="maxIdle" value="200" /> <!-- 每次释放连接的最大数目 --> <property name="numTestsPerEvictionRun" value="1024" /> <!-- 释放连接的扫描间隔(毫秒) --> <property name="timeBetweenEvictionRunsMillis" value="30000" /> <!-- 连接最小空闲时间 --> <property name="minEvictableIdleTimeMillis" value="1800000" /> <!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 --> <property name="softMinEvictableIdleTimeMillis" value="10000" /> <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 --> <property name="maxWaitMillis" value="1500" /> <!-- 在获取连接的时候检查有效性, 默认false --> <property name="testOnBorrow" value="true" /> <!-- 在空闲时检查有效性, 默认false --> <property name="testWhileIdle" value="true" /> <!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true --> <property name="blockWhenExhausted" value="false" /> </bean>2.修改后:
修改之后: <!-- 连接池配置 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!-- 最大连接数:根据实际压测调整,建议 150 左右 --> <property name="maxTotal" value="100" /> <!-- 最大空闲连接数:与 maxTotal 保持一致,避免频繁创建销毁 --> <property name="maxIdle" value="100" /> <!-- 最小空闲连接数:保底连接,应对突发流量 --> <property name="minIdle" value="10" /> <!-- 每次驱逐检查的连接数,适当降低 --> <property name="numTestsPerEvictionRun" value="50" /> <!-- 驱逐扫描间隔:30 秒 --> <property name="timeBetweenEvictionRunsMillis" value="30000" /> <!-- 设为 -1,避免硬驱逐破坏 minIdle --> <property name="minEvictableIdleTimeMillis" value="-1" /> <!-- 空闲超过该值且当前空闲连接数>minIdle 时释放 --> <property name="softMinEvictableIdleTimeMillis" value="60000" /> <!-- 获取连接最大等待时间:3 秒 --> <property name="maxWaitMillis" value="3000" /> <!-- 关闭 testOnBorrow,减少 PING 命令 --> <property name="testOnBorrow" value="false" /> <!-- 开启空闲检测,定期剔除无效连接 --> <property name="testWhileIdle" value="true" /> <!-- 连接耗尽时阻塞等待,而不是直接抛异常 --> <property name="blockWhenExhausted" value="true" /> </bean>二、Jedis 连接池前后配置对比 & 连接数、连接生命周期变化分析
先整理核心参数变更清单,再分维度讲连接数量、空闲连接、新建 / 销毁、获取连接行为的变化,最后总结线上表现差异。
一、参数完整变更对照表
| 参数 | 修改前 | 修改后 | 变更影响核心说明 |
|---|---|---|---|
| maxTotal | 200 | 100 | 全局最大连接上限减半,整个池子最多只能持有 100 条 TCP 连接 |
| maxIdle | 200 | 100 | 空闲连接上限同步减半,池子不会保留超过 100 条空闲连接 |
| minIdle | 无配置(默认 0) | 10 | 新增最小空闲保底,池子会主动维持至少 10 条空闲连接应对突峰 |
| numTestsPerEvictionRun | 1024 | 50 | 每次后台驱逐线程扫描连接数大幅降低,减少大量 PING 扫描开销 |
| minEvictableIdleTimeMillis | 1800000(30 分钟) | -1 | 关闭「强制驱逐空闲连接」硬阈值,不再单纯靠空闲时长销毁连接 |
| softMinEvictableIdleTimeMillis | 10s | 60s | 软驱逐空闲阈值拉长到 1 分钟;仅当空闲连接 > minIdle (10) 时才会释放超时空闲连接 |
| maxWaitMillis | 1500ms | 3000ms | 获取连接等待超时翻倍,排队拿连接允许等待更久 |
| testOnBorrow | true | false | 拿连接时不再发 PING 校验,减少 Redis 命令开销,但拿到死连接概率小幅上升 |
| testWhileIdle | true | true | 不变,后台线程定期校验空闲连接有效性 |
| blockWhenExhausted | false | true | 连接打满时策略反转:原直接抛异常,现阻塞等待 maxWait 时长再超时 |
二、Redis 客户端连接数相关变化(分 4 个维度)
- 全局最大 TCP 连接总量上限:大幅下降
修改前:池子最多创建 200 条 Redis TCP 连接;
修改后:上限压缩为 100 条;
现象:client list 看到的客户端总连接峰值直接减半,Redis 服务端连接占用更低。 - 空闲连接数量变化(最关键改动)
(1)保底空闲:新增常驻 10 条连接
修改前无 minIdle,空闲连接可能被全部驱逐到 0,流量突增时需要频繁新建 TCP 连接;
修改后池子后台会自动补连接,始终保证至少 10 条空闲就绪连接,突发流量不会大量新建连接。
(2)空闲连接上限从 200→100
池子不会堆积超过 100 条空闲连接,避免大量闲置 TCP 占用 Redis 文件句柄。
(3)空闲连接销毁逻辑彻底改变
旧逻辑:
空闲超过 30 分钟 → 直接强制销毁,不管当前池子缺不缺连接;
空闲超 10 秒,且空闲数 > 200 → 释放;
容易出现:低峰期连接被大量清到 0,高峰期疯狂建连接。
新逻辑:
minEvictableIdleTimeMillis=-1:取消 30 分钟强制销毁;
仅满足两个条件才销毁空闲连接:
① 空闲时长超过 60 秒;
② 当前空闲连接数 > minIdle (10);
效果:
低峰期最少保留 10 条空闲,不会清空;
只有空闲连接多于 10 条、且闲置满 1 分钟,才会慢慢释放多余连接;
连接复用率大幅提升,TCP 新建 / 断开次数显著减少。
(4)空闲扫描压力降低
旧:每次驱逐扫描 1024 个连接,大量 PING 命令压 Redis;
新:每次只扫描 50 条,后台校验开销骤降,Redis CPU 更低。 - 流量高峰时新建连接、排队行为变化
(1)连接耗尽策略反转 blockWhenExhausted
旧:blockWhenExhausted=false
池子达到 maxTotal (200) 后,立刻抛出 NoSuchElementException,请求直接失败,不会排队;
新:blockWhenExhausted=true
池子打满 100 条后,新请求会排队等待空闲连接释放,最多等 3 秒,超时才报错;
表现:高峰期不会瞬时大量报错,请求排队缓冲,但极端高并发下会出现接口耗时拉高。
(2)获取连接等待时间变长
maxWait 从 1.5s → 3s,排队拿连接容错窗口更大,降低瞬时并发报错率。 - 连接有效性校验对连接存活的影响
旧:testOnBorrow=true
每次拿连接先 PING 校验,无效直接丢弃、新建连接;死连接会即时清理,但每次请求多一条 Redis 命令,性能损耗大;
新:testOnBorrow=false,仅靠 testWhileIdle 后台巡检清理无效连接;
减少 PING 开销,但极端场景下可能拿到已断连的 TCP,执行命令时报连接异常,业务需要捕获重试;
后台驱逐线程每 30 秒扫描,会逐步剔除失效空闲连接,不会长期堆积死连接。
三、整体连接侧线上表现总结
Redis 服务端总连接数峰值减半,文件句柄占用更低;
低峰期不会清空空闲连接,常驻 10 条保底,减少 TCP 频繁创建销毁;
多余空闲连接销毁更温和,只有闲置 1 分钟且超过 10 条才释放,连接复用率更高;
连接打满时不再快速报错,改为最多阻塞 3 秒等待空闲连接;
减少大量 PING 校验命令,降低 Redis CPU 消耗;
后台扫描连接数量减少,驱逐逻辑更轻量化;
风险点:最大连接从 200 降到 100,若业务真实并发超过 100,会出现大量请求排队、接口延迟上升,需压测确认 100 连接是否满足业务峰值。
四、潜在风险提醒(连接数相关)
maxTotal 下调至 100,若业务并发高,会出现排队等待连接,接口 RT 上涨;
testOnBorrow 关闭后,存在极低概率拿到断连连接,业务需做好 Redis 异常重试;
softMinEvictableIdleTimeMillis 延长至 60s,流量低谷会保留更多空闲 TCP,短期占用 Redis 连接,但换来了更少的重连开销。
三、帮我解释一下,修改之前,客户端连接池波动非常大,我们有39个节点,修改之前,有的时候客户端连接数突然变成几千,然后降下来,经常这样,修改之后呢,平稳在1300左右,没有大幅度波动了。不知道什么原因
一、先搞懂旧配置为什么会暴涨到几千连接、剧烈波动
你们 39 个服务节点,每个节点旧配置maxTotal=200,理论总上限 39*200=7800,这是基数,但真正导致瞬间冲几千、反复震荡的核心是 4 个参数缺陷:
1. 旧驱逐逻辑会把空闲连接全部清 0,流量一上来疯狂新建连接
旧配置:
minIdle未配置,默认 0;minEvictableIdleTimeMillis=1800000(30 分钟强制驱逐);softMinEvictableIdleTimeMillis=10000(仅空闲 > maxIdle 才释放)
行为拆解:
- 业务低峰、流量下滑后,后台驱逐线程会持续清理空闲连接;
- 只要一条连接空闲满 30 分钟,不管池子需不需要,直接销毁;
- 没有最小空闲保底,最后池子空闲连接会被清到 0;
- 流量突然上涨(定时任务、峰值接口、批量查询),池子里面一条可用连接都没有,只能瞬间大批量新建 TCP 连接;
- 短时间内几百个新建连接打到 Redis,多节点叠加,客户端连接数瞬间飙升几千;
- 流量回落,连接又慢慢被驱逐销毁,数字断崖下跌,形成大幅波动。
2.blockWhenExhausted=false,连接耗尽直接抛异常,业务重试放大建连
旧配置:blockWhenExhausted="false"池子连接打满 200 后,新请求不排队、直接报错。 业务代码捕获 Redis 异常后大多会做重试逻辑,重试又会再次向池子申请连接,恶性循环: 瞬时大量新建连接 → Redis 连接数暴涨 → 连接耗尽 → 大量报错重试 → 继续新建连接,峰值直接拉满。
3.testOnBorrow=true每次拿连接 PING,断连即时销毁,加剧反复创建
每次获取连接都发 PING 校验,只要 TCP 失效立刻丢弃这条连接。 网络抖动、Redis 短暂卡顿、防火墙空闲断连时,大量连接被当场销毁,池子瞬间缺连接,又要批量新建,放大连接波动。
4. numTestsPerEvictionRun=1024,驱逐线程扫描过猛,频繁销毁空闲连接
每次后台巡检一次性扫描 1024 条连接,只要满足空闲阈值就批量销毁,低峰期空闲连接被快速清空,进一步加剧 “空池子 + 峰值爆建连” 的震荡。
补充:多节点叠加效应
39 个业务节点,每个节点池子独立。单个节点瞬间新建几十上百条连接,39 个节点同时触发峰值,Redisclient list看到总连接直接冲到几千,流量回落又快速下跌,波动极其明显。
二、新配置为什么稳定卡在 1300 左右,不再剧烈抖动
新配置从保空闲、缓驱逐、排队限流、减少销毁四个维度根治波动,逐条对应解决上面问题:
1. 新增 minIdle=10,池子永远保留保底空闲连接(最核心稳连接手段)
每个节点强制常驻至少 10 条空闲连接:
- 低峰期驱逐线程不会把连接清到 0;
- 流量突增时,直接复用现成空闲连接,不需要大批量新建 TCP; 从根源杜绝 “瞬间爆发式建连”,连接总数不会脉冲式冲高。
2. 关闭强制驱逐 minEvictableIdleTimeMillis=-1,只做温和软驱逐
旧:空闲 30 分钟强制销毁; 新:取消硬性销毁规则,只有同时满足两点才释放多余连接: ① 空闲超过 60 秒;② 当前空闲连接 > minIdle (10) 效果: 只会释放超出保底的多余空闲连接,保底 10 条永远不动,不会大面积清空池子,连接数量不会断崖下跌。
3. blockWhenExhausted=true,连接打满排队等待,消除异常重试风暴
池子达到 maxTotal=100 上限后,新请求不再立刻报错,而是阻塞等待最多 3 秒:
- 不会抛出大量 Redis 连接异常;
- 业务无异常就不会触发重试;
- 杜绝 “报错重试→疯狂新建连接” 的正反馈暴涨; 并发流量平缓消化,不会瞬间冲击 Redis 连接。
4. testOnBorrow=false,减少连接即时销毁
不再每次拿连接 PING,网络轻微抖动不会批量丢弃有效连接; 只有后台 testWhileIdle 定时巡检才清理失效连接,连接销毁是缓慢、少量的,不会短时间大量缺连接。
5. numTestsPerEvictionRun=50,驱逐扫描轻量化
每次只扫描 50 条连接,低峰期不会一次性批量销毁大量空闲连接,连接释放节奏平缓,无断崖下跌。
6. 单节点 maxTotal 从 200 降到 100,总连接上限收窄,峰值天花板降低
单节点最大连接从 200 压缩到 100,39 节点理论上限 39*100=3900,相比原来 7800 上限减半; 同时有 minIdle 保底、排队机制削峰,实际稳定运行总连接维持在 1300,不会冲到几千。
三、1300 稳定值怎么来的(估算佐证)
39 个节点,每个节点常态连接占用平均: 1300 ÷ 39 ≈ 33 条 / 节点 每个节点:保底空闲 10 条 + 业务活跃占用 20 多条,合计三十多条,完全符合新配置运行特征:
- 不会空池爆建连;
- 不会无限制堆积空闲;
- 连接打满会排队缓冲并发; 整体总量被牢牢稳住,无脉冲式暴涨暴跌。
四、总结一句话因果
旧配置无最小空闲、强制批量驱逐、连接耗尽直接报错触发重试、每次拿连接校验销毁失效连接,低峰池子被清空,峰值瞬间大批量新建连接,39 节点叠加后连接数冲几千剧烈波动; 新配置常驻 minIdle 保底连接、取消强制销毁、连接耗尽阻塞排队减少重试、轻量化驱逐逻辑,流量起伏时无需瞬时大量新建 / 销毁连接,总连接数被平滑控制在 1300 区间,波动消失。
今天 09:35
如何解决连接耗尽直接抛异常的问题?
如何优化连接池配置以提高性能?
一句话因果
旧配置无最小空闲、强制批量驱逐、连接耗尽直接报错触发重试、每次拿连接校验销毁失效连接,低峰池子被清空,峰值瞬间大批量新建连接,39 节点叠加后连接数冲几千剧烈波动; 新配置常驻 minIdle 保底连接、取消强制销毁、连接耗尽阻塞排队减少重试、轻量化驱逐逻辑,流量起伏时无需瞬时大量新建 / 销毁连接,总连接数被平滑控制在 1300 区间,波动消失。
今天 09:35
如何解决连接耗尽直接抛异常的问题?
如何优化连接池配置以提高性能?
如何监控和管理连接池?