maxActive=100 写在那,HikariCP 就是不认——连接池迁移遗漏引发的生产事故

本文是Spring Boot 生产配置实战系列的第 4 篇 叙事框架:现象 → 排查过程 → 根因 → 修复 → 预防

问题现象

周三上午 10:15,告警群突然响了——user-center 的 p99 响应时间飙到 5.8 秒,错误率 8.1%,大量用户登录超时。

告警群讨论

值班小A登录监控一看,CPU 才 30.5%,load average 却高达 15.3。

监控告警面板

CPU 才 30%,但 load 15——明显不是计算瓶颈,进程大概率在等什么资源。

排查过程

第一步:系统层面排查

登录到 user-center-prod-01:

top 系统资源

CPU 空闲 59.7%,但 iowait 8.0%,load 从 10 到 15 还在涨。RAM 和 Swap 正常。这指向 IO 层面的问题——大概率是数据库连接。

第二步:connection 数异常

netstat 看 MySQL 连接:

netstat 连接数

只有 10 个 MySQL 连接,全部 ESTABLISHED,没有空闲。 10 这个数字扎眼了——HikariCP 默认的 maximumPoolSize 正好是 10。

第三步:日志确认

翻应用日志,全是连接池排队超时:

应用日志连接超时

Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available,
request timed out after 30000ms (total=10, active=10, idle=0, waiting=63)

total=10, active=10, idle=0, waiting=63——10 个连接全被占了,63 个线程在排队。

第四步:查配置,发现问题

回到开发环境看 application.yml:

配置文件检查

spring:
  datasource:
    url: jdbc:mysql://db-prod:3306/user_center
    username: uc_app
    password: ********
    driver-class-name: com.mysql.cj.jdbc.Driver
    maxActive: 100
    initialSize: 10
    minIdle: 5
    maxWait: 30000

maxActive: 100initialSize: 10——这是 Druid 的参数!

项目以前用 Druid,迁移到 Spring Boot 2.7 时去掉了 Druid 依赖(pom.xml 改了),但 application.yml 里这些 Druid 参数全留下了。HikariCP 根本不认这些参数,静默走默认值 maximumPoolSize=10

而且用户中心每次登录要查 3 次数据库(用户信息 → 权限 → 组织),10 个连接高峰时根本不够用。

根因分析

两层问题叠加:

第 1 层:Druid → HikariCP 迁移遗漏

$ git log --oneline -8
c4d5e6b chore: 迁移至 Spring Boot 2.7,移除 Druid 依赖

迁移只改了 pom.xml,application.yml 里的 Druid 参数 没人删、没人替换。HikariCP 启动时看到 maxActiveinitialSize 等陌生参数直接跳过,没有任何 warning。

第 2 层:HikariCP 默认连接数不够

用户中心每个 POST /api/user/login 查 3 张表(user → permissions → organization),每次查都需要一个连接。加上每个查询内有 30-80ms 的等待(Thread.sleep 模拟外部调用),高峰期 30 个并发请求就能把 10 个连接打满。

连接被打满后,后续请求在 connectionTimeout=30000ms(默认 30 秒)内排队等待。前端等不了 30 秒,5 秒就超时重试→重试更多请求→排队队列暴涨→雪崩。

修复方案

删除 Druid 参数,添加 HikariCP 配置

spring:
  datasource:
    url: jdbc:mysql://db-prod:3306/user_center?useSSL=false
    username: uc_app
    password: ********
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10
      connection-timeout: 5000
      idle-timeout: 300000
      max-lifetime: 600000
      pool-name: UserCenterPool

修复配置对比

关键改动:

操作 参数 旧值 新值 备注
删除 maxActive 100 Druid 参数,HikariCP 不识别
删除 initialSize 10 同上
删除 minIdle 5 同上
删除 maxWait 30000 同上
新增 maximumPoolSize 50 HikariCP 正确参数
新增 connectionTimeout 5000 从默认 30s 改为 5s,快速失败
新增 minimumIdle 10 保留空闲保活

修复后的配置文件

添加连接池监控端点

部署后需要一个接口随时查看连接池状态:

连接池监控代码

if (dataSource instanceof HikariDataSource hds) {
    result.put("activeConnections",
        hds.getHikariPoolMXBean().getActiveConnections());
    result.put("idleConnections",
        hds.getHikariPoolMXBean().getIdleConnections());
    result.put("totalConnections",
        hds.getHikariPoolMXBean().getTotalConnections());
    result.put("threadsAwaitingConnection",
        hds.getHikariPoolMXBean().getThreadsAwaitingConnection());
}

验证结果

部署 v2 后,连接池恢复正常:

验证修复

{
    "activeConnections": 6,
    "idleConnections": 14,
    "totalConnections": 20,
    "threadsAwaitingConnection": 0,
    "maximumPoolSize": 50,
    "connectionTimeoutMs": 5000
}
  • activeConnections 从 10 降到 6(不再打满)
  • threadsAwaitingConnection 从 63 降到 0(无排队)
  • p99 响应从 5.8s 降到 38ms
  • 错误率从 8.1% 降到 0%

避坑建议

1. 迁移连接池 = 改两个文件

从 Druid 切到 HikariCP 不只是改 pom.xml:

✅ pom.xml:       移除 Druid 依赖 → 加上 spring-boot-starter-jdbc
✅ application.yml: 删除 Druid 参数 → 添加 HikariCP 参数

缺一不可。建议迁移完成后 grep -E 'maxActive|initialSize|filters|Druid' application.yml 确认无残留。

2. HikariCP 对陌生参数静默忽略

这是最坑的地方。Druid 的 maxActiveinitialSizeminIdle 等参数配置在 spring.datasource.* 下,HikariCP 启动时不报错、不警告,直接走默认值。如果没有连接池监控,你根本不知道参数没生效。

3. connectionTimeout 不要用默认 30s

30 秒超时意味着一场故障需要 30 秒才暴露。在这 30 秒内 Tomcat 线程全在等连接,前端重试加剧雪崩。建议 3-5 秒,快速失败比缓慢窒息好。

4. 连接池必须配监控

加一个 /api/user/pool-status 端点暴露 HikariPoolMXBean,随时可以看: - threadsAwaitingConnection > 0 是连接池告警的核心指标 - activeConnections 接近 maximumPoolSize 说明要扩了

5. 配置文件 review 纳入迁移流程

这次事故的核心是 配置文件和依赖不同步。团队流程里加一个检查点:改 pom.xml 后必须审计 application.yml 中对应的配置参数。

附:完整命令清单

系统排查

top -b -n 1 | head -25                           # 系统资源概览
netstat -ant | grep ':3306 '                     # 查看 MySQL 连接
ss -ant 'sport = :3306 or dport = :3306'         # 同上(更快)
awk '{print $6}' | sort | uniq -c | sort -rn     # 连接状态统计
iostat -x 1 3                                    # IO 状况

应用日志排查

tail -200 app.log | grep -E 'ERROR|Connection|hikari' | head -25
grep -c 'Connection is not available' app.log
grep 'Connection is not available' app.log | awk -F'[:,]' '{print $NF}' | sort | uniq -c | sort -rn

连接池配置审计

grep -rn 'maxActive\|initialSize\|minIdle\|filters' src/main/resources/   # 检查 Druid 残留
grep -rn 'hikari\|maximumPoolSize\|connectionTimeout' src/main/resources/ # 检查 HikariCP 配置

连接池运行时状态

curl -s http://localhost:8080/api/user/pool-status | python3 -m json.tool

Git 排查迁移历史

git log --oneline --all --grep='druid\|Druid\|datasource\|连接池'
git show <commit>:pom.xml | grep -E 'druid|hikari|jdbc'

📖 完整版带可复现 Demo → opencao.cn 📺 公众号「Ai拆代码的曹操」 🌟 知识星球「源阅会」(82877104)