上周五凌晨2点37分,我负责的电商促销系统监控突然发出刺耳的警报声。登录服务器查看发现,核心订单服务的响应时间从平时的50ms飙升到12秒以上,前端页面大量报错"数据库连接超时"。通过SHOW PROCESSLIST命令查看MySQL服务端,赫然发现连接数已经达到max_connections上限的800个,且大量连接处于Sleep状态超过300秒。
这种情况在流量高峰时段尤为致命。我们的订单服务采用标准的连接池架构,理论上应该自动管理连接生命周期。但现实情况是,连接数像雪球一样越滚越大,最终耗尽所有资源。更诡异的是,即使业务量回落后,连接数依然居高不下,必须重启服务才能暂时缓解。
关键诊断命令:
sql复制SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections'; SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND='Sleep';
以Java生态常用的HikariCP为例,这几个参数直接影响连接泄漏:
java复制HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(50); // 最大连接数
config.setMinimumIdle(10); // 最小空闲连接
config.setIdleTimeout(60000); // 空闲连接超时(毫秒)
config.setConnectionTimeout(30000); // 获取连接超时
config.setMaxLifetime(1800000); // 连接最大存活时间
参数设计陷阱:
通过arthas工具追踪,我们发现了几类典型泄漏模式:
java复制// 错误示例
@Transactional
public void processOrder() {
// 发生异常时事务不会自动回滚
throw new RuntimeException("模拟异常");
}
java复制try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM large_table")) {
// 遍历ResultSet时发生异常
while (rs.next()) {
if(rs.getInt("id") == 123) throw new RuntimeException();
}
} // 虽然用了try-with-resources,但网络中断时连接仍可能泄漏
code复制MySQL服务器端:
wait_timeout = 300 # 非交互连接超时(秒)
interactive_timeout = 600 # 交互连接超时
HikariCP配置:
idleTimeout = 700000 # 700秒 > wait_timeout
根据MySQL官方建议调整后的配置:
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 50
minimum-idle: 5
idle-timeout: 290000 # 比wait_timeout少10秒
max-lifetime: 1800000 # 30分钟
connection-timeout: 30000
leak-detection-threshold: 60000 # 泄漏检测阈值
validation-timeout: 5000
connection-test-query: SELECT 1
重要原则:连接池的超时参数必须小于数据库服务器的对应参数
事务模板改进:
java复制@Transactional(rollbackFor = Throwable.class) // 明确指定回滚异常类型
public void safeTransaction() {
// 业务逻辑
}
资源关闭最佳实践:
java复制public void safeQuery() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.createStatement();
rs = stmt.executeQuery("SELECT ...");
// 处理结果集
} finally {
try { if(rs != null) rs.close(); } catch (SQLException e) { log.error(e); }
try { if(stmt != null) stmt.close(); } catch (SQLException e) { log.error(e); }
try { if(conn != null) conn.close(); } catch (SQLException e) { log.error(e); }
}
}
Prometheus监控指标:
yaml复制- name: db_connection_usage
query: |
sum(processlist_count) by (instance)
/ on(instance)
group_left max_connections by (instance)
alert:
expr: db_connection_usage > 0.8
for: 5m
labels:
severity: critical
annotations:
summary: "数据库连接使用率超过80% (instance {{ $labels.instance }})"
Grafana看板关键图表:
SHOW GLOBAL STATUS LIKE 'Threads_connected'SHOW PROCESSLIST中非Sleep状态的连接比例SHOW STATUS LIKE 'Threads_created'增长速率当发现异常连接时,通过以下SQL定位问题应用:
sql复制SELECT
PROCESSLIST_ID as pid,
USER,
HOST,
DB,
COMMAND,
TIME,
STATE,
LEFT(INFO, 100) as query
FROM
performance_schema.threads
WHERE
TYPE='FOREGROUND'
ORDER BY
TIME DESC;
配合网络层抓包确认真实来源IP:
bash复制tcpdump -i eth0 -nn -s0 port 3306 | grep -E 'connect|quit'
HikariCP自检:
java复制HikariPoolMXBean poolProxy = hikariDataSource.getHikariPoolMXBean();
System.out.println("活跃连接: " + poolProxy.getActiveConnections());
System.out.println("空闲连接: " + poolProxy.getIdleConnections());
System.out.println("等待线程: " + poolProxy.getThreadsAwaitingConnection());
System.out.println("总连接: " + poolProxy.getTotalConnections());
Arthas实时监控:
bash复制# 监控连接获取情况
watch com.zaxxer.hikari.pool.HikariPool getConnection '{params,returnObj,throwExp}'
# 追踪连接关闭
trace com.zaxxer.hikari.pool.ProxyConnection close
某金融系统每日凌晨跑批时出现连接池耗尽。分析发现:
解决方案:
某社交平台使用JPA时出现连接泄漏,根源在于:
java复制@Transactional
public void updateUser(User user) {
// 触发懒加载
user.getFriends().forEach(friend -> {
friend.setLastActive(new Date());
});
// 集合操作可能超出事务范围
}
优化方案:
@BatchSize注解spring.jpa.open-in-view=false配置在测试环境定期注入以下故障:
每日凌晨执行的检查脚本示例:
python复制def check_connections():
max_conn = execute_sql("SHOW VARIABLES LIKE 'max_connections'")[1]
used_conn = execute_sql("SHOW STATUS LIKE 'Threads_connected'")[1]
if int(used_conn) > int(max_conn) * 0.7:
alert(f"连接数预警: {used_conn}/{max_conn}")
long_conn = execute_sql("""
SELECT COUNT(*)
FROM information_schema.PROCESSLIST
WHERE TIME > 300 AND COMMAND='Sleep'
""")[0]
if long_conn > 10:
kill_connections = execute_sql("""
SELECT GROUP_CONCAT(CONCAT('KILL ',id,';'))
FROM information_schema.PROCESSLIST
WHERE TIME > 300 AND COMMAND='Sleep'
""")
execute_sql(kill_connections)
根据实际业务场景选择:
| 特性 | HikariCP | Druid | Tomcat JDBC |
|---|---|---|---|
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 监控功能 | 基础 | 全面 | 中等 |
| SQL防火墙 | 不支持 | 支持 | 不支持 |
| 适合场景 | 高并发OLTP | 需要监控的企业应用 | 传统JavaEE应用 |
| 连接泄漏检测 | 60秒阈值 | 多种检测机制 | 基本检测 |
在微服务架构中,建议采用HikariCP+Druid双池策略:前者用于核心业务,后者用于后台管理类应用。