news 2026/11/5 5:28:54

虚拟线程压到 1 万并发,HikariCP 还是 10 连接:Spring Boot 3.2 池大小怎么算

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
虚拟线程压到 1 万并发,HikariCP 还是 10 连接:Spring Boot 3.2 池大小怎么算

本文摘要:虚拟线程 1 万并发,HikariCP 默认 10 连接耗尽。池大小按目标 TPS 和 SQL 耗时反推,受 MySQL 上限约束。公式仅适配 JDBC 同步模型,异步或读多写少场景需另选。

一、问题与结论

一台服务从 Spring Boot 3.2.0 升到 3.2.5,加了一行spring.threads.virtual.enabled=true,期望靠虚拟线程扛住压测。压测脚本打到 1 万并发后,接口大面积返回 503,日志里反复出现同一行:

SQLTransientConnectionException: main-pool - Connection is not available, request timed out after 5000ms.

(该报错为按下方步骤推演的预期结果,本机未跑满 1 万并发实测,属未验证。)

复现命令:

# 环境:JDK 21、Spring Boot 3.2.5、Docker MySQL 8.0dockerrun-d--namemysql-pool-eMYSQL_ROOT_PASSWORD=root-eMYSQL_DATABASE=testdb-p3306:3306 mysql:8.0curl"http://localhost:8080/stress?concurrency=10000"

结论先摆出来:连接池大小不要跟随虚拟线程数量,要按目标 TPS × 单条 SQL 平均持有连接时间反推,再受 MySQL 的max_connections封顶。默认 10 个连接在 1 万并发下必然耗尽;但把maximum-pool-size直接调到 1000 同样失败,数据库侧会先拒绝连接。

二、排查与选择依据

先确认瓶颈是不是连接池,而不是 SQL 本身。压测期间读hikaricp.connections.active与hikaricp.connections.pending两个指标:pending接近 9990、active恒等于maximum-pool-size,说明请求全部堵在取连接这一步。

计算路径

四步走,每步都有可观测输入:

  1. 定目标 TPS:压测基线或业务峰值估算,例:2000。
  2. 测单条 SQL 平均持有连接时间:含事务边界,用 APM span 或 MySQL 侧统计,例:10ms,即0.01s。
  3. 算下界:池大小 ≥ 目标 TPS × 持有时间秒 = 2000 × 0.01 = 20。
  4. 校验上界与排队:MySQL 8.0 默认max_connections=151,可用数还要减去运维连接;同时并发数 × 持有时间 / 池大小必须小于connection-timeout,本例10000 × 0.01 / 20 = 5s,等于默认 5000ms 的临界值,要么把connection-timeout提到30000,要么在入口限流。

两个失败方向要同时看:池太小 → 超时报 503;池太大 → 启动或运行时抛Too many connections,因为 MySQL 侧先到顶。

替代方案与取舍

方案选择条件代价边界
按 TPS 反推池大小(本文)JDBC 同步阻塞、SQL 耗时稳定高并发下排队延迟上升持有时间波动大时会击穿超时
R2DBC +r2dbc-pool全异步非阻塞栈、WebFlux全链路改写、团队学习成本只适配响应式栈
读写分离 + 缓存读多写少、单条 SQL 耗时高引入一致性与缓存失效复杂度写路径基本无收益
入口信号量限流需要提前拒绝、保护下游与连接池内部排队重复一层必须自定义背压与降级策略

单条 SQL 耗时波动剧烈、或事务里夹了远程调用时,不该用本文的平均值公式,此时持有时间不可预测,池大小算得再准也会被长尾拖垮。

三、关键原理

HikariCP 官方 wiki 的经验公式是connections = ((core_count * 2) + effective_spindle_count)。它假设每个平台线程持有一个连接、且瓶颈在数据库 CPU,因此按核数加倍。这个前提在虚拟线程下不成立:1 万个虚拟线程同时存在,但 JDBC 驱动依然是阻塞模型,一个连接同一时刻只服务一个请求。

所以约束方换了位置。虚拟线程消掉的是应用侧的并发上限,连接这类外部有限资源仍然稀缺(JDK 21 文档对此有明确说明)。吞吐上限近似为池大小 ÷ 单连接持有时间:持有 10ms,20 个连接的理论上限就是 2000 TPS。再加连接,吞吐不增,只会让排队变长。

另一个 JDK 21 的坑:业务代码里出现synchronized(this) { ... jdbc.query(...) },虚拟线程无法从载体线程上卸载(即 pinning),实际并发度仍被平台线程池限制。排查池大小前,先用 JDK 22 或改用ReentrantLock排除这一层干扰。

四、可运行示例

application.yml:

spring:threads:virtual:enabled:truedatasource:url:jdbc:mysql://localhost:3306/testdbusername:rootpassword:roothikari:pool-name:main-poolmaximum-pool-size:10minimum-idle:5connection-timeout:5000

压测接口:

@RestControllerpublicclassStressController{privatefinalJdbcTemplatejdbc;privatefinalExecutorServicevt=Executors.newVirtualThreadPerTaskExecutor();publicStressController(JdbcTemplatejdbc){this.jdbc=jdbc;}@GetMapping("/stress")publicStringstress(@RequestParam(defaultValue="10000")intn)throwsException{CountDownLatchlatch=newCountDownLatch(n);AtomicIntegerok=newAtomicInteger();AtomicIntegerfail=newAtomicInteger();longt0=System.nanoTime();for(inti=0;i<n;i++){vt.submit(()->{try{jdbc.queryForObject("SELECT id FROM t1 WHERE id = ?",Long.class,1L);ok.incrementAndGet();}catch(Exceptione){fail.incrementAndGet();}finally{latch.countDown();}});}latch.await(60,TimeUnit.SECONDS);longms=(System.nanoTime()-t0)/1_000_000;return"concurrency=%d, success=%d, failed=%d, elapsed=%dms".formatted(n,ok.get(),fail.get(),ms);}}

建表与数据:

dockerexecmysql-pool mysql-uroot-proot-e"USE testdb; CREATE TABLE IF NOT EXISTS t1(id BIGINT PRIMARY KEY); INSERT IGNORE INTO t1 VALUES(1);"

启动后先打/stress?concurrency=1000摸底,再逐级到 10000。

五、验证结果与边界

预期输出(maximum-pool-size: 10、connection-timeout: 5000、单条 SQL 约 10ms、1 万并发):

concurrency=10000, success≈5000, failed≈5000, elapsed≈5100ms

成功数按池大小 × connection-timeout ÷ 单条 SQL 耗时估算:10 × 5000ms ÷ 10ms = 5000,即约一半请求在 5 秒内拿到连接完成查询,其余等满超时后返回 503。同时应观察到hikaricp.connections.pending接近 5000、hikaricp.connections.active恒为 10。

实际输出:本机未跑满 1 万并发压测,上述数字为按公式推算的预期值,属未验证,请按第四节步骤本地复现后填入。

常见失败:把maximum-pool-size调到 1000 后,HikariCP 建连时报java.sql.SQLNonTransientConnectionException: Too many connections。原因是 MySQL 8.0 默认max_connections=151,数据库先拒绝。先确认当前上限再调:

dockerexecmysql-pool mysql-uroot-proot-e"SHOW VARIABLES LIKE 'max_connections'; SHOW STATUS LIKE 'Threads_connected';"

修复方式是按上界校验结果把池压回 151 以内,或在my.cnf中调大max_connections并同步评估服务器内存与每连接的sort_buffer_size类开销。调参顺序固定为:测持有时间 → 定目标 TPS → 算下界 → 校验max_connections→ 分阶段放量。

思考

  1. 单条 SQL 耗时随数据量波动时,按平均值算出的池大小会不会周期性击穿connection-timeout?
  2. 连接池排队与入口信号量都提供背压,二者叠用时应该由哪一层先拒绝请求?

参考资料

  • HikariCP Wiki:About Pool Sizing
  • JDK 21 官方文档:Virtual Threads
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 5:21:47

DyberPet 桌面宠物快速上手:十分钟,桌面多一位会说话的室友

DyberPet 桌面宠物快速上手&#xff1a;十分钟&#xff0c;桌面多一位会说话的室友 【免费下载链接】DyberPet Desktop Cyber Pet Framework based on PySide6 项目地址: https://gitcode.com/GitHub_Trending/dy/DyberPet 下午对着需求文档走神&#xff0c;屏幕角落的像…

作者头像 李华
网站建设 2026/11/5 5:17:20

Jasna 上手全解:视频马赛克修复的免费安装、配置与排错

Jasna 上手全解&#xff1a;视频马赛克修复的免费安装、配置与排错 【免费下载链接】jasna Video restoration tool 项目地址: https://gitcode.com/GitHub_Trending/ja/jasna Jasna 是一款免费的视频马赛克修复工具&#xff0c;专门解决"想把视频里的马赛克去掉、…

作者头像 李华
网站建设 2026/11/5 5:13:19

CodiumAI PR-Agent:革命性AI代码审查助手快速上手指南

CodiumAI PR-Agent&#xff1a;革命性AI代码审查助手快速上手指南 【免费下载链接】pr-agent &#x1f680; PR Agent: The Original Open-Source PR Reviewer. This project is not the Qodo free tier. 项目地址: https://gitcode.com/GitHub_Trending/pr/pr-agent &a…

作者头像 李华
网站建设 2026/11/5 5:11:46

FreeTube性能监控:如何查看和优化资源占用

FreeTube性能监控&#xff1a;如何查看和优化资源占用 你是否遇到过FreeTube使用时卡顿、加载缓慢或内存占用过高的问题&#xff1f;本文将从性能监控和优化两个维度&#xff0c;帮助你全面掌握FreeTube的资源管理技巧&#xff0c;让应用运行更流畅。读完本文后&#xff0c;你…

作者头像 李华