
凌晨两点半,监控告警突然炸了------线上服务响应时间飙到10秒以上,Redis的响应延迟突破500ms。登录服务器一看,redis-cli client list显示连接数暴涨到2000+,而我们的连接池配置上限明明是100。重启服务后一切恢复正常,但半小时后连接数又开始缓慢爬升...... 如果你也遇到过这种"神秘的连接泄漏",今天这篇复盘或许能帮你少熬一次夜。
1. 场景还原:高并发支付系统的"慢性死亡"
当时我们在做一个促销活动的支付结算服务,QPS峰值3000左右,用的是Jedis连接池,配置如下:
java
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(50);
config.setMinIdle(10);
// 没有设置 testOnBorrow/testOnReturn!
JedisPool pool = new JedisPool(config, "redis.master", 6379);
问题出在服务运行一周后:连接数慢慢涨到上限,新请求开始阻塞等待连接。
2. 为什么连接不归还?
- 根因是网络闪断导致的连接僵死。很多同学以为连接池会自己处理失效连接,实际上:
- Jedis默认不验证连接有效性(除非显式设置
testOnBorrow) - TCP层的KeepAlive默认要2小时才能发现断连
- 应用代码如果没有正确释放连接,连接会一直被占用
最坑的是,这类问题在低并发时根本发现不了------连接池有足够余量兜底。一旦流量上来,泄漏速度超过连接池回收速度,系统就会突然崩掉。
3. 错误写法 vs 正确姿势
看看这段典型的错误代码:
java
// 错误示例:没有try-with-resources也没有手动close
public void updateUserOrder(String userId) {
Jedis jedis = pool.getResource(); // 这里拿连接
jedis.hset("user:orders", userId, "paid");
// 如果中间抛出异常,连接永远不会归还!
}
正确的做法至少有三种:
- 方案1:try-finally手动归还
java
Jedis jedis = pool.getResource();
try {
jedis.hset("user:orders", userId, "paid");
} finally {
if (jedis != null) jedis.close(); // 关键!
}
- 方案2:try-with-resources(Java 7+)
java
try (Jedis jedis = pool.getResource()) { // 自动close
jedis.hset("user:orders", userId, "paid");
}
- 方案3:Spring的RedisTemplate
**```java
// 配置声明式事务后,Spring会帮我们处理连接生命周期
@Transactional
public void updateUserOrder(String userId) {
redisTemplate.opsForHash().put("user:orders", userId, "paid");
}
### 4. 数据对比:泄漏 vs 健康状态
我们在测试环境模拟了同样的问题:
| 场景 | 连接数(1小时后) | 平均RT | 错误率 |
|-----------|-----------|---------|------|
| 无泄漏 | 10\~20波动 | 3ms | 0% |
| 每次请求漏1个连接 | 涨到100上限 | 1200ms↑ | 23%↑ |
| 不设连接上限 | 突破3000+ | 完全挂掉 | 100% |
### 5. 避坑清单:连接池必知的6个要点
1.** always close**:无论在try-catch块还是lambda里,确保每个`getResource()`都有对应的close
*** 配置有效性检测**:生产环境必须设置`testOnBorrow`或`testWhileIdle`
*** 限制池大小**:不给maxTotal设限等于埋雷
*** 监控连接数**:对`pool.getNumActive()`做告警
*** 避免跨线程共用**:一个线程拿的连接,绝不能在另一个线程归还
*** 小心事务模式**:MULTI/EXEC期间连接会被独占,超时时间设短些
### 终极解法:让框架替你操心
后来我们把所有Redis操作迁移到了Spring Data Redis + `@Transactional`方案。框架帮我们处理了:
* 连接获取/释放
* 事务边界
* 异常时的连接回滚
再也没有为连接问题熬过夜。
所以你看,**连接池泄漏从来不是"连接池"的问题,而是使用姿势的问题。你们项目里是怎么管理Redis连接的?有没有更优雅的解法?评论区聊聊你的实战经验。