结论先行:单一指标做不了提前量,真正能「提前 30s~2min 报警」的是 pending(等待队列深度)的趋势,而不是 active==maxSize。 你现在的报警点太靠后,根因不是指标选错,而是把「结果态」(active 打满)当成了预警点。下面按你四个问题逐条给可落地方案。
1. 先纠正一个指标名 + 确认默认暴露面
Spring Boot 3.x + Micrometer 下,HikariCP 通过 HikariDataSource 的 MBean 自动注册,默认就有这几个 gauge,无需额外打点:
| Micrometer 指标名 | 含义 | 采集方式 |
|---|---|---|
| hikaricp_connections_pending | 此刻正在等连接的线程数(最早信号) | 自动,/actuator/metrics/hikaricp_connections_pending |
| hikaricp_connections_active | 已借出未归还数 | 自动 |
| hikaricp_connections_idle | 池中空闲可用数 | 自动 |
| hikaricp_connections_total | active+idle | 自动 |
| hikaricp_connections_usage | active/total 占比 | 自动(派生,=active/total) |
注意:你写的 hikaricp_connections_acquire_wait 不是 Micrometer 默认指标,HikariCP 的 MBean 也没暴露「获取连接耗时」这个 gauge。想要「等待时长分位数」得自己加一个(见下)。所以第一档预警请用 pending 绝对值 + 趋势,别等 acquire_wait。
如果确实要「等待时长」分位数,最干净的做法是自定义打点(包一层 DataSource,或用 Micrometer 的 Timer 包住 getConnection):
@Bean
public DataSource dataSource(DataSourceProperties props, MeterRegistry registry) {
HikariDataSource ds = props.initializeDataSourceBuilder().type(HikariDataSource.class).build();
// 包一层,记录「进池等待→拿到连接」的耗时
Timer t = registry.timer("hikaricp.acquire.wait");
return new FilteredDataSource(ds, t); // FilteredDataSource 在 getConnection 前后打点
}
// 注意:这是「等待耗时」,不是「持有耗时」,别混
2. 阈值怎么定才有提前量(核心:用 Little's Law 反推基线)
别拍脑袋定 pending=10。 用 Little's Law 算你这套(800 QPS 稳态 / 1500 峰值,maxSize=30)的「健康 pending 基线」:
所需并发连接 ≈ QPS × 平均持有时长
- 稳态 800 QPS:若单次 DB 往返 + 事务持有 ≈ 5ms,则 800×0.005 = 4 个并发连接就够;若持有 50ms,则需 40 个。
- 峰值 1500 QPS:同理放大。
关键判断:你 maxSize=30,若健康态「所需并发」就逼近 30,那 pending 在稳态就会长期 >0,这时 pending 的绝对值没意义,要用**「pending 的斜率 / 持续时长」**做预警。若健康态 pending≈0,那 pending>0 持续 N 秒就是真信号。
可落地阈值(给你三档,带提前量):
| 档位 | 条件 | 提前量 | 说明 |
|---|---|---|---|
| P3 预警 | pending > 2 且持续 30s | 提前 ~1min | 队列开始形成,还没打满。用 for: 30s,避免瞬时毛刺 |
| P2 压力 | pending > 10 或 usage > 0.8,持续 60s | 提前 ~30s | 明显承压,还没雪崩 |
| P1 打满 | active == maxSize 或出现 SQLTransientConnectionException | 已出事 | 这档是「事故通报」,不该是首道防线 |
为什么 for: 30s 这么关键:你观察到 pending 从 0→50 只要 8~15s,所以用绝对值单点触发必然滞后;改成「pending 越过低阈值后持续 30s 不回落」,就能在 8~15s 的爬坡窗口内抓到,比 active==max 早至少一个量级。
最有力的提前量其实不是阈值,是 connectionTimeout:你现在的 connectionTimeout 大概率还是默认 30s(见 #214 的教训)。把它降到 2~3s,效果是——一旦池子开始吃紧,请求 2~3s 就快速失败并触发你的业务侧超时告警,这比任何 pending 阈值都早、都准。#18 和 #214 都指向这一点:connectionTimeout=2000ms 配合熔断快速失败,是「提前量」的真正来源,pending 告警只是它的可观测镜像。
3. 慢 SQL + 线程持有混合:单指标会漏,要组合
你排除了慢 SQL,但「慢 SQL + 线程持有」是两种不同病灶,单看 pending 无法区分:
- 慢 SQL 主导:
active高 +pending涨 + MySQL 侧Threads_running也高(连接真在跑 SQL)。 - 线程持有主导(#18 的「远程调用穿插在事务里」):
active高 +pending涨 + MySQL 侧Threads_running低(连接占着但 DB 空闲,在等 RPC)。
组合判据(建议写进告警规则):
# 真正的池压力 = pending 持续 + active 贴顶,且排除「DB 其实很闲」
pending > 5 for 30s
AND active / maxSize > 0.8
AND (mysql_threads_running > 10 OR db_idle_ratio < 0.3) # 任一成立才算真压力
再加一条反向探针专门抓「线程持有」:
active > maxSize * 0.9
AND mysql_threads_running < 5 # DB 很闲但连接占满 = 持有问题
这条能直接命中 #18 里「连接被持有 1.1s、DB 实际只跑 60 会话」的场景,比单纯看 pending 准得多。
4. 报警分级与通知
| 级别 | 触发 | 通知谁 | 动作 |
|---|---|---|---|
| P3 | pending>2 持续 30s | 服务 owner(IM,不电话) | 看板看一眼,记录趋势 |
| P2 | pending>10 / usage>0.8 持续 60s | on-call + DBA(电话) | 检查慢日志 + SHOW ENGINE INNODB STATUS 区分等连接/等锁 |
| P1 | active==maxSize / 连接超时异常 | war-room + SRE | 限流降级,查持有源头 |
配套必做(比阈值更重要,来自 #18/#214 的共识):
connectionTimeout从默认 30s 降到 2000ms,配熔断——这是提前量本体;leakDetectionThreshold=30000ms默认开,抓「借出未归还」;maxLifetime别 <60s(#18 提到调到 30min 后重建率降 90%)。
一句话:你缺的不是「选哪个指标」,而是把 pending 的持续时长 当预警、把 connectionTimeout 当提前量本体、把 mysql_threads_running 当「慢 SQL vs 线程持有」的判别器——三者组合,才能在 active 打满前 30s~2min 出声。