开放问答
JDK 21 虚拟线程替换 Tomcat 线程池后 HikariCP 连接池被打满:pinning 排查与池大小重估怎么做?
该问题的正文、回答与评论。
提问者 林景行 · 状态 open
虚拟线程HikariCP高并发性能调优JDK21
【背景】 我负责一个订单查询服务:Spring Boot 3.2 + JDK 21(虚拟线程,spring.threads.virtual.enabled=true)+ HikariCP 5.1 + MySQL 8.0。核心接口 QPS 约 3000,链路是「查订单主表 → 批量查订单明细 → 查用户标签(Redis)→ 聚合返回」,其中订单明细是一次 IN 查询,单请求 1~2 次 DB 往返,平均 DB 耗时 8ms。
【现象】
切到虚拟线程之前(Tomcat 线程池 200),HikariCP 配 maximumPoolSize=50,P99 稳定 45ms,一切正常。切到虚拟线程后,压测 QPS 拉到 3500 时 HikariCP 的 pending 线程数持续堆积,P99 从 45ms 飙到 600ms+,-Djdk.tracePinnedThreads=short 没打出明显 pin 栈,-XX:+EnableVirtualThreadsPinningDiagnostics 的 JFR 事件也很少。DB 侧 slow log 干净,连接数没打满(50/50 但 pending 长期 >100)。
【已尝试的排查】
- 把 maximumPoolSize 从 50 拉到 500:P99 反而更差(DB 侧出现锁等待),已回退;
- 检查 synchronized 代码块:业务代码里有一处
synchronized保护本地缓存 map(非热点路径),怀疑是 pin 点,改成 ReentrantLock 后现象没变; - 用 arthas thread -n 5 看 carrier 线程栈,大部分在 park,看不到明显的 DB 等待栈。
【期望得到的答案形态】
- 在「虚拟线程 + 阻塞型 JDBC」场景下,HikariCP 池大小应该怎么重估?有没有比「CPU 核数倍数」更靠谱的公式或经验值(比如按 DB 往返耗时 × 目标 QPS / 单连接吞吐来算)?
- 如何系统性确认 pinning 是否存在(JFR 事件、tracePinnedThreads 的正确打开方式)?HikariCP 5.x 是否已经适配虚拟线程(它的内部锁实现)?
- 如果最终结论是「连接池大小不变、瓶颈在 DB 单连接吞吐」,那虚拟线程的收益到底体现在哪一段?
欢迎有相同迁移经验的同学分享具体参数与数据。
评论
暂无评论。