连接池耗尽服务全挂,排查3天才发现HikariCP默认值全是坑

简介: 本文记录一次线上服务雪崩事故的3天排查过程:核心服务RT从80ms飙升至30秒,根源竟是HikariCP三个开发默认值——maximum-pool-size=10、connection-timeout=30s、idle-timeout=10分钟——在高并发下形成乘法级灾难。文章详解问题原理,并给出生产级配置方案。

那天凌晨3点,告警短信把整组人炸醒了。

监控大屏上,一个核心服务的RT从平时的80ms飙到了30秒,所有接口全部超时。看Tomcat线程池——200个线程全部在跑,没有一个空闲。再看数据库——MySQL连接数只有12个,远没到上限151。数据库不慢,服务却崩了。

运维第一反应是重启。重启后好了10分钟,又崩。再重启,又是10分钟。

接下来3天的排查,最后锁定的问题只有一行配置。

排查第一天:看起来只是"慢"

从日志入手,错误信息很明确:

java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, 
request timed out after 30000ms.
    at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:734)

翻译成人话:获取数据库连接等了30秒还没拿到,直接抛异常

这是 HikariCP 连接池的典型报错——连接池里的连接不够用了,新的请求排队等了 connection-timeout 的时长(默认30秒),还是没等到,报错退出。

第一天的排查方向:是不是有慢SQL把连接占住了?查了MySQL慢查询日志,确实有一条统计SQL跑了3秒——原因是某个表昨天新加了200万行数据,负责统计的索引没建。

加了索引后,SQL从3秒降到50ms。重启服务,以为解决了。

凌晨4点,又崩了。一模一样的报错。

排查第二天:200多个线程全卡在一个地方

第二天做了线程dump。不看不知道,一看吓一跳:

"http-nio-8080-exec-187" #187 daemon prio=5 os_prio=0 tid=... waiting on condition
   java.lang.Thread.State: TIMED_WAITING
        at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:358)
        - waiting on <0x00000006c0d4e8f0>
"http-nio-8080-exec-188" #188 daemon prio=5 os_prio=0 tid=... waiting on condition
   java.lang.Thread.State: TIMED_WAITING
        at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:358)
        - waiting on <0x00000006c0d4e8f0>

从 #187 到 #200+,所有线程都在 HikariPool.getConnection() 上排队。Tomcat的200个线程池全部被这些等待连接的线程占满了,新来的请求根本没线程处理,直接被拒绝

问题变成了:为什么连接池里的连接不够用?

看了一下配置——问题初步浮出水面:

# 当前配置(就是Spring Boot的默认值,一直没改过)
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.connection-timeout=30000

10个连接。这个服务的正常QPS是100,每个数据库查询平均50ms,理论上 10 / 0.05 = 200 QPS 上限,100 QPS 应该够用。

但问题在于:当出现一个3秒的慢查询时,整个池瞬间就崩了

第三天:终于找到了根因——不是1个参数的锅,是3个默认值联手搞的事

算一笔账,你就能明白为什么"10个连接+30秒超时"是生产环境的定时炸弹:

灾难推演:假设QPS=100,10个连接。当1条SQL从50ms突变为3秒(缺索引/数据量暴涨/锁等待),有效吞吐量从 10/0.05=200 QPS 骤降到 10/3=3.3 QPS。每秒钟有 97 个请求在排队等连接。每个请求最多等 connection-timeout=30秒才报错。30秒 × 97个/秒 = 2910个请求在Tomcat线程池里排队。而Tomcat默认只有200个线程——线程池先于连接池被击穿,整个服务假死。

重新审视 HikariCP 的默认值,有3个参数是专门为开发机设计的,不是为生产环境设计的

参数

默认值

开发机的逻辑

生产环境的现实

maximum-pool-size

10

本地就一个用户,10个连接绰绰有余

生产QPS 100+,一条慢SQL就让10个连接全部占满

connection-timeout

30000(30秒)

本地请求少,等久一点无所谓

30秒超时意味着线程池被排队线程占满,其他正常请求全被拒绝

idle-timeout

600000(10分钟)

连接池小,回收快慢无所谓

空闲连接10分钟才回收,连接长时间不用可能已被数据库端断开,下次使用直接报错

单个看,每个默认值都有它的道理。但三个组合在一起,高并发场景下的效果是乘法级的灾难

  • maximum-pool-size=10 → 吞吐天花板极低
  • connection-timeout=30000 → 出问题时线程堵30秒才退出,堵住的线程又占着Tomcat线程池
  • idle-timeout=600000 → MySQL端 wait_timeout 如果设了较短值(比如某些云数据库默认60秒),连接在池里闲置10分钟早就被数据库关了,取出来用就报 CommunicationsException

三个参数的Production-Ready配置

1. maximum-pool-size:不是越大越好,但要算清楚

先看数据库能承受多少连接:

-- 查数据库最大连接数
SHOW VARIABLES LIKE 'max_connections';
-- 查当前已用连接数
SHOW STATUS LIKE 'Threads_connected';

假设MySQL最大连接数151,系统账号和监控占20个,剩下131个给业务。如果线上有4个应用实例,每个实例的连接池大小上限 = 131 / 4 ≈ 32。

然后再用业务公式验证:

# 公式:连接池大小 = 峰值QPS × 平均数据库查询RT(s) × 1.5
# 示例:峰值QPS=200,平均SQL RT=50ms=0.05s
# → 200 × 0.05 × 1.5 = 15 个连接
# 但必须保留慢查询buffer:再预留30%
# → 15 × 1.3 ≈ 20 个连接
spring.datasource.hikari.maximum-pool-size=20

一个经验值:单实例一般配 20~50,不要超过数据库最大连接数 ÷ 实例数的 80%。10 是绝对不够的。

2. connection-timeout:30秒太长了,3秒就够了

你在生产环境等过一个30秒的请求吗?用户早就关页面了。

connection-timeout 的逻辑是"等多久还没拿到连接就抛异常"。30秒意味着:

  • 用户等了30秒才看到报错(他10秒前就已经关了页面)
  • 这30秒里Tomcat线程一直被占用,堵着后面的正常请求
  • 如果30秒内有3000个请求进来,3000个线程全堵着——Tomcat线程池瞬间爆炸
# 设置3秒超时——如果3秒拿不到连接,说明连接池大概率已经崩了
# 快速失败比长时间等待要好得多
spring.datasource.hikari.connection-timeout=3000

快速失败原则:3秒拿不到连接就报错,用熔断/降级兜底,而不是让所有请求堵30秒然后把整个服务拖死

3. idle-timeout 和 max-lifetime:别让你的连接被数据库偷偷关了

最后一个坑:HikariCP里的连接闲置太久,数据库那边早就断开了,拿出来用就报错。

# MySQL默认 wait_timeout = 28800(8小时),一般不改
# 但很多云数据库RDS的默认值不同,有些只有60秒
# HikariCP 的 max-lifetime 必须比数据库的 wait_timeout 短
# 一般设 30分钟(1800000ms)
spring.datasource.hikari.max-lifetime=1800000  # 30分钟,默认值OK
# idle-timeout 要小于 max-lifetime
# 设置5分钟——连接5分钟没用就回收,不让数据库端先断开
spring.datasource.hikari.idle-timeout=300000    # 5分钟
# 额外加 keepalive ——定期ping保持连接活跃(HikariCP 4.0+ 支持,最低间隔30秒)
spring.datasource.hikari.keepalive-time=60000   # 每60秒ping一次

一张表记住所有配置

参数

默认值(坑)

生产建议值

为什么

maximum-pool-size

10

20~50

按 QPS×RT×1.5 计算

connection-timeout

30000

3000

快速失败,堵30秒会拖死线程池

idle-timeout

600000

300000

5分钟回收,防止数据库端先断

max-lifetime

1800000

1800000

默认OK,但必须小于MySQL wait_timeout

keepalive-time

0(关闭)

60000

每60秒ping保活

写在最后

HikariCP 本身是一个非常好的连接池——启动快、开销低、性能顶级。问题不在于它,在于默认值是为"本地开发"这个场景设置的。Spring Boot 把它作为默认连接池时,并没有把默认值改成生产级别的。

这件事的教训不只是"记得改配置"——上线前检查清单里,连接池配置必须是必查项。10个连接在本地跑得飞起,到了生产就是一颗不知道什么时候爆炸的定时炸弹。

你的项目里 HikariCP 的这几个参数改过了吗?还在用默认值的话,下次高并发可能就不是排查3天的问题了。

相关文章
|
6天前
|
缓存 人工智能 安全
GPT-5.6 Terra与GPT-5.5性能实测:成本减半后的跑分对比与快速迁移指南
GPT-5.6 Terra 的定价为每百万 token 输入 2.50/输出 15。GPT-5.5 则是 5/ 30。Terra 的每一项费率,包括 $0.25/M 的缓存读取,都恰好是 GPT-5.5 的一半,因此在任何工作负载组合下,Terra 都固定 便宜 2.0x。以每天 10 万次请求、3K token 提示词计算,大约是 Terra 每天 2,000,GPT−5.5每天 4,000,即每月约 60,000对 120,000。问题在于:OpenAI 没有发布任何针对 Terra 的编码基准。那个著名的 91.9% Terminal-Bench 数字是 Sol 在 Ul
|
1天前
|
数据采集 机器学习/深度学习 人工智能
田间杂草定位与检测4200张YOLO智慧农业数据集分享
本数据集含4200张真实农田图像,YOLO格式,单类别(杂草)高质量标注,覆盖多作物、多光照、多生长阶段等复杂场景,专为智慧农业杂草检测与智能除草设备研发设计,支持YOLOv5/v8/v10等主流模型训练。
326 93
|
2天前
|
缓存 UED 开发者
Codex109天重置23次,明天还要再送一次
Codex近109天完成23次额度重置,7月14日将迎来第24次。Tibo高频响应用户反馈:优化GPT-5.6高消耗问题、补发失效福利、调整重置时间——形成“反馈→回应→修复→补偿”正向闭环,彰显以用户为中心的产品哲学。(239字)
488 11
|
6天前
|
人工智能 缓存 JSON
刚刚 GPT-5.6 发布,吊打 Claude 5 和 Grok 4.5?一手实测来啦!
GPT-5.6 刚发布,跑分号称超越 Fable 5,真的假的?附一手实测,让 3 个最强 AI 编程模型 GPT-5.6 Sol、Claude Fable 5、Grok 4.5 在 Cursor 里同时一把梭开发网页游戏,看看最新模型到底谁更能打。
332 0
|
6天前
|
存储 人工智能 JSON
Qwen 本地部署搭配 ComfyUI 生成 AI 漫剧完整实操指南(小白零基础可落地,零成本无限生成+角色一致性天花板)
2026全网最优本地漫剧流水线:零成本、离线运行、角色统一、低配(8G显卡)可跑。融合Qwen本地大模型+ComfyUI双引擎,实现剧本生成→分镜绘图→动态成片全自动,隐私安全、无审核限流,新手30分钟上手,日更无忧。(239字)