连接池到底配多大?QPS 和 RT 算一遍,再跑 1 万次请求实测超时率

同一份参数,我只改连接池上限,让 3000 个请求轮流去抢连接。

池配 5,1728 个请求拿到超时,超时率 56.86%,平均要等 816 毫秒。

池配 20,一个都没超时,平均等待 1.13 毫秒。

再往上一档,池配 40,一样是 0% 超时,只是连接平均利用率从 75.66% 掉到 37.83%。

我关心的 跑出来的结果
QPS 60 / RT 30ms / 池 2 0% 超时,但平均等待 49.75ms、P95 150ms
QPS 60 / RT 30ms / 池 20 0% 超时,平均等待 0ms,利用率 9.39%
QPS 300 / RT 50ms / 池 5 56.86% 超时,平均等待 816.27ms,最长队列 351
QPS 300 / RT 50ms / 池 20 0% 超时,平均等待 1.13ms,利用率 75.66%
QPS 500 / RT 60ms / 池 20 24.36% 超时,最长队列 556
自检断言 默认参数 17/18 通过,加压参数 18/18 通过

一句话结论:池小于 QPS 乘 RT 再除以 1000,超时率会陡起来;池大于这条线,再加池只是浪费连接。

成品是一个 index.html,双击就能跑,不联网、不引第三方库、没有构建步骤。

案例目录在这里,下载下来直接打开:

text 复制代码
https://atomgit.com/deli007/demo_park/tree/main/codearts-connection-pool-lab

这是一个跑在浏览器里的排队沙盘,不是压测工具。

它的请求是页面自己按泊松过程造出来的,连的是内存里的假连接,所以它算的是「参数配错会怎样」,不是真实数据库能扛多少 QPS。

为什么值得看:连接池配小了不报错,只会超时

连接池大小是少数几个配错不会报错的参数。

配小了,监控上只是偶尔冒出几个获取连接超时;配大了,连接数上去了,数据库那头的连接数上限又顶不住。

常见的经验公式「核数乘 2 加 1」,在数据库这条链路上基本没用。

真正决定池大小的是三个数:到达速率、每次查询要占用连接多久、你能容忍多长的排队等待。

这三个数都是能算的,我把它做成一个页面,一秒一秒地摊开给你看。

一、先看结果:池大小和超时率之间有一条线

我把 QPS、RT、获取超时(1000ms)、模拟时长(10 秒)、随机种子(42)都固定,只动池上限。

QPS RT 池上限 负载 ρ 总请求 超时数 超时率 平均等待 P95 等待 最长队列 利用率
60 30ms 2 0.90 628 0 0% 49.75ms 150ms 15 92.90%
60 30ms 5 0.36 628 0 0% 0.54ms 0ms 5 37.55%
60 30ms 10 0.18 628 0 0% 0ms 0ms 0 18.77%
60 30ms 20 0.09 628 0 0% 0ms 0ms 0 9.39%
300 50ms 5 3.00 3039 1728 56.86% 816.27ms 1000ms 351 99.90%
300 50ms 20 0.75 3039 0 0% 1.13ms 9ms 9 75.66%
300 50ms 40 0.38 3039 0 0% 0ms 0ms 0 37.83%
500 60ms 20 1.50 5091 1240 24.36% 799.20ms 1000ms 556 99.76%
500 60ms 35 0.86 5091 0 0% 1.67ms 10ms 13 86.96%

ρ 是负载,等于 QPS 乘 RT 再除以 1000 乘池上限。

前四行藏着一个容易被忽略的事:QPS 60、RT 30ms、池只给 2,超时率是 0%。

不是因为它够用,而是因为获取超时填了 1000ms,而最长排队也没超过 150ms。

池 2 那行连接利用率 92.90%,池 5 直接掉到 37.55%。

池从 2 加到 5,换来的不是「不超时」,是「不用排队」。

后面四行是真正翻车的场景:负载 ρ 超过 1 之后,队列会一直涨。

QPS 300、池 5 的时候最长队列到 351,P95 等待顶在 1000ms,那是被获取超时掐掉的。

二、池大小的那条线,就在 QPS 乘 RT 除以 1000

QPS 300、RT 50ms,一个连接每秒最多服务 20 个请求(1000 除以 50),300 个请求需要 15 个连接。

页面上那条曲线,池从 1 开始的 83% 一路往下,到 14 左右贴到 0%,之后一直是 0%。

15 就是这条线:QPS 乘 RT 除以 1000。

比它小,排队时间会随负载指数式往上走;比它大,超时率就一直是 0。

至于要大多少,取决于你愿意接受多长的排队,这部分下一页再说。

三、超时阈值才是超时率的旋钮

池不变,只改获取连接超时,同一份流量给出三组完全不同的数字。

池上限 获取超时 超时数 超时率 平均等待 吞吐
5 100ms 2009 66.11% 93.91ms 103.00 req/s
5 1000ms 1728 56.86% 816.27ms 131.10 req/s
5 5000ms 473 15.56% 2750.77ms 256.60 req/s

QPS 300、RT 50ms、池 5,负载 ρ 一直是 3,池一点没动。

把获取超时从 100ms 放到 5000ms,超时率从 66.11% 掉到 15.56%,看起来好看了四倍多。

但平均等待从 93.91ms 一路涨到 2750.77ms。

超时率、等待时间、吞吐这三个数是一起动的,把超时调大只是把「失败」换成了「变慢」。

真实系统里,那个 2750ms 的等待会占用上游的线程和连接,代价只是从数据库挪到了应用层。

四、理论公式和实测,差的不是精度

页面右边那栏用 Erlang-C(M/M/c)算理论值。

池 2、QPS 60、RT 30ms 的时候,理论给的排队概率是 85.26%,而实测超时率是 0%。

这两个数不是一回事:P(wait) 说的是「需要不需要排队」,超时率说的是「排队有没有超过你设的阈值」。

拿 P(wait) 当超时率看,池 2 会被误判成重度故障。

反过来,池足够大的时候,理论的平均系统请求数还是能对上的:QPS 300、池 20 时理论 15.481,实测 16.033,差 3.56%。

所以理论值的正确用法是判断「池够不够」,不是预测「会有多少请求失败」。

五、怎么做与踩坑:这不是一把过的

我在码道 Web 里把需求写清楚:1 毫秒一个时间步、泊松到达、每个请求占一个连接 RT 毫秒带正负 20% 抖动、等待超过获取超时就记失败、页面上要有自检面板。

第一版生成出来,逻辑看着对,跑起来全是错的。

最要命的一处:连接用完是用 setTimeout 归还的,而整个模拟循环是同步跑完的,回调在循环结束前根本不会执行。

结果就是第 21 个请求之后连接再也不释放。默认参数跑出来 600 个请求、520 个超时、超时率 86.67%,而同样参数的理论排队概率应该接近 0。

第二处是自检面板一片空白。runAssertions 里写了一行 bindParam(...)(),而 bindParam 不返回函数,直接抛 TypeError,异常没人接住,面板就空着。

第三处在理论对照栏:理论值是 0 的时候,偏差百分比算出了 9105379845733382%。

我把这三条整理成一份缺陷清单发回去,要求用模拟时间推进、不要用任何真实时间 API,自检外面套 try/catch。

修复版把引擎改对了(每个连接记一个 busyUntil 释放时刻),但合并的时候把 runAssertions 定义了两遍。

多出来的那份还带着一个没有 try 的 catch,整个页面直接白屏,点什么都没反应。

我把重复的那份删掉,页面才跑出上面这些数字。

六、验证与踩坑:18 条断言里那条红的

页面加载后自动跑 18 条断言,覆盖输入夹紧、可复现性、指标一致性、单调性、过载判定。

默认参数(QPS 60、RT 30ms、池 20)下是 17/18,唯一红的是「RT 抖动生效(等待有变化)」这条。

它断言平均等待大于 0。可池够大的时候连接随手就有,平均等待真就是 0,这条断言必然红。

这是断言本身写错了,不是代码错了,我把它如实留在了页面里。

换成 QPS 300、RT 50ms、池 20 之后,18 条全绿。

七、失败路径:RT 填 0 会怎样

我把 RT 输入框手动改成 0。

页面立刻在下面给出红字提示「RT 不能为 0,已自动设为 1」,同时把参数夹回 1,没有白屏。

上一次的运行结果也还在,没被清掉。

同类的夹紧还有两条:QPS 填 0 会被夹到 1,池上限填负数会被夹到 1,自检面板里都有对应的 PASS。

已知的边界也交代一下:获取超时输入框因为滑块步长是 100,只能填 100 的整数倍。

而且想表示「不超时」填 0 是不生效的,页面读到 0 会当成没填、回落到默认的 1000ms,这一条我没改,写在页面里当已知问题。

八、本地复现

页面是单文件,不需要构建:

text 复制代码
python3 -m http.server 8080

然后在浏览器里打开 http://localhost:8080/index.html,或者直接双击 index.html。

要复核里面的数字,改完参数点「开始模拟」,种子保持 42,同一份参数会给出完全一样的结果。

自检面板在页面最下面,18 条断言的通过数和实际值都在那里。

九、准备环境:进入码道 Web

码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。

浏览器打开码道 Web 版:devcloud.cn-north-4.huaweicloud.com/chat?source...,登录后就能在对话窗口输入需求,不需要装软件。

十、使用码道体会

这次最值钱的经验是「把验收方式写进需求」。

我在需求里写明「页面上要有自检面板,逐条显示 PASS/FAIL 和实际值」,交付时就有了 18 条可点可验的断言,文章里的测试数才有出处。

第二点是交付后一定要自己跑一遍。

上面那三个缺陷,看代码看不出来,跑一次默认参数就露馅了------86.67% 的超时率太反常,顺着反常的数字往回查,才找到 setTimeout 那一行。

第三点是修复轮要带清单,不要只说「再优化一下」。

我把「哪个函数、什么现象、期望值是多少」一条条写清楚,修复才落到了点上;即便如此,合并时还是会出岔子,所以修完必须重跑一遍验收。

十一、总结

连接池配多大,先算 QPS 乘 RT 除以 1000。

低于这条线,超时率会陡起来,怎么调超时都只是把失败换成变慢。

高于这条线,接着决定你能接受多长的排队等待,再留一点余量就够了,多配的连接只是闲在那里占着数据库的连接数。

案例目录(下载下来双击就能跑):

text 复制代码
https://atomgit.com/deli007/demo_park/tree/main/codearts-connection-pool-lab

全部实测数据和自检结果都能在页面里自己复现,种子填 42 就是我这篇里的数字。

相关推荐
风花一世月1 小时前
🏮 我把三坊七巷做成了能飞的 3D 地图(901 个真实建筑轮廓、20 处景点,在线直接玩)
前端
特立独行的猫A1 小时前
用仓颉语言写一个串口调试助手:cj-tauri 实战
前端
小凯在掘金1 小时前
Shadow DOM 和 Virtual DOM ?别再傻傻分不清
前端·html
郑州光合科技余经理1 小时前
本地生活平台搭建:跨业态用户标识怎么贯通
java·开发语言·前端·后端·uni-app·php·ai编程
数据掘金1 小时前
埋点事件命名升级时老数据怎么办
前端
对空六课1 小时前
Vue 组件曝光埋点为什么会重复触发?去重方案与实现思路
服务器·前端·算法·数据分析
isha1 小时前
Univer拆解——从类与方法的视角分析
前端·javascript
羲云1 小时前
不想再让用户手写 Markdown:零依赖的所见即所得编辑器最新版发布 V0.2.0
前端
deli0071 小时前
限流算法到底怎么选?令牌桶 vs 漏桶 vs 滑动窗口 3 种同屏实测
前端