1. 问题现象与初步诊断
上周五下午3点,我们的电商平台突然出现大面积服务不可用,前端页面大量报错"数据库连接超时"。登录服务器查看MySQL监控,发现连接数从平时的200激增到800(max_connections=1000),CPU利用率达到95%。最致命的是,这些连接中有75%处于Sleep状态,实际活跃连接不到100个。
这种情况在Java应用中最常见,特别是使用HikariCP或Druid连接池时。连接池中的连接被获取后没有正确释放,导致连接数不断累积。我立即做了三件事:
- 执行
SHOW PROCESSLIST查看当前连接详情 - 检查应用日志中的SQL异常
- 用
netstat确认TCP连接状态
2. 连接泄漏的根因定位
2.1 线程堆栈分析
通过jstack导出Java应用的线程快照,发现大量线程卡在同一个DAO方法的getConnection()调用上。进一步检查代码,发现如下问题代码:
java复制// 错误示例:没有在finally中关闭连接
public List<Order> getRecentOrders(int userId) {
Connection conn = dataSource.getConnection();
// 执行查询...
// 如果这里抛出异常,连接永远不会关闭!
return orderList;
}
2.2 MySQL服务端验证
在MySQL执行以下诊断命令:
sql复制-- 查看不同用户连接数分布
SELECT user, COUNT(*) as connections
FROM information_schema.processlist
GROUP BY user;
-- 查看长时间空闲连接
SELECT * FROM information_schema.processlist
WHERE Command = 'Sleep'
AND Time > 300; -- 超过5分钟的空闲连接
结果显示90%的Sleep连接都来自同一个应用服务,证实了连接泄漏的猜测。
3. 紧急处理方案
3.1 临时扩容与连接回收
立即采取以下措施缓解问题:
- 临时调整MySQL参数(风险提示:需要重启)
ini复制max_connections = 2000 # 默认1000 wait_timeout = 300 # 默认28800(8小时) interactive_timeout = 300 - 使用管理员账号手动清理空闲连接:
sql复制-- 批量kill空闲连接 SELECT CONCAT('KILL ', id, ';') FROM information_schema.processlist WHERE user = 'app_user' AND Command = 'Sleep' INTO OUTFILE '/tmp/kill_threads.sql'; source /tmp/kill_threads.sql;
3.2 连接池配置优化
调整HikariCP配置(示例):
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 50 # 根据实际负载调整
idle-timeout: 60000 # 1分钟空闲超时
max-lifetime: 1800000 # 30分钟最大生命周期
leak-detection-threshold: 5000 # 5秒泄漏检测
connection-timeout: 3000 # 3秒获取连接超时
4. 根本解决方案
4.1 代码层修复
强制使用try-with-resources语法:
java复制public List<Order> getRecentOrders(int userId) {
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(SQL)) {
// 执行查询...
return orderList;
} // 自动关闭连接
}
4.2 监控体系建设
-
部署Prometheus监控:
yaml复制# MySQL Exporter配置 - job_name: 'mysql' static_configs: - targets: ['mysql-exporter:9104'] # 应用连接池监控 - job_name: 'app' metrics_path: '/actuator/prometheus' static_configs: - targets: ['app:8080'] -
关键监控指标:
mysql_global_status_threads_connectedhikaricp_active_connectionshikaricp_idle_connectionshikaricp_pending_threads
4.3 防御性编程实践
- 添加连接泄漏检测拦截器:
java复制@Aspect
@Component
public class ConnectionLeakAspect {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object checkConnection(ProceedingJoinPoint pjp) throws Throwable {
int before = getActiveConnCount();
Object result = pjp.proceed();
int after = getActiveConnCount();
if(after > before) {
log.error("连接泄漏 detected in {}.{}",
pjp.getSignature().getDeclaringTypeName(),
pjp.getSignature().getName());
}
return result;
}
}
5. 深度防御措施
5.1 连接池压力测试
使用JMeter模拟并发场景,重点观察:
- 连接获取等待时间
- 连接回收效率
- 连接泄漏率
测试脚本应包含:
- 正常业务场景
- 异常场景(SQL超时、事务回滚)
- 长时间运行(24小时稳定性)
5.2 故障演练方案
建立定期演练机制:
- 模拟连接泄漏(修改wait_timeout为60秒)
- 观察监控告警是否触发
- 验证自动恢复脚本
- 检查熔断机制是否生效
5.3 架构级优化
对于高并发场景建议:
- 引入读写分离
- 使用ShardingSphere分库分表
- 热点数据迁移到Redis
- 考虑使用云数据库的连接池服务
6. 典型问题排查指南
| 现象 | 可能原因 | 验证方法 | 解决方案 |
|---|---|---|---|
| 连接数缓慢增长 | 连接泄漏 | jstack分析 | 修复未关闭连接的代码 |
| 连接数突然飙升 | 流量激增 | 检查QPS监控 | 扩容+限流 |
| 大量连接处于Login状态 | 认证问题 | 查看错误日志 | 检查账号权限 |
| 连接获取超时 | 连接池耗尽 | 查看pending_threads | 调整连接池大小 |
7. 性能调优参数参考
MySQL服务端关键参数:
ini复制# 连接相关
max_connections = 2000
wait_timeout = 600
interactive_timeout = 600
thread_cache_size = 100
# 性能相关
innodb_buffer_pool_size = 12G # 物理内存的70%
innodb_io_capacity = 2000
innodb_flush_neighbors = 0 # SSD建议关闭
HikariCP推荐配置公式:
code复制最大连接数 = (核心数 * 2) + 有效磁盘数
例如:8核CPU + SSD => (8*2)+1 = 17
实际取值建议20-50之间
8. 长效治理机制
- 代码扫描规则:检测未关闭的连接
- CR强制检查点:资源释放逻辑
- 每周连接数趋势分析
- 每季度连接池压测
- 建立连接泄漏应急预案手册
这套方案实施后,我们的系统连续6个月未再出现连接池爆满问题。最关键的是培养了团队三个习惯:
- 所有数据库操作必须用try-with-resources
- 所有SQL必须有超时设置
- 所有数据库变更必须经过连接池影响评估