Locust压力测试实战:QPS、TPS与接口性能瓶颈定位
上周有个朋友拿着他们刚上线的订单接口来找我说测试环境点了上百次都挺顺一上生产就零星超时,日志里还看不出名堂。我问他压过没有他说拿 Postman 手动跑了二十几个请求,看着响应都挺快。这就是典型的把“功能验证”当成了“压力测试”。接口能不能用是一回事扛不扛得住真实流量是另一回事。后来我们用 Locust 压了一轮,发现是数据库连接池太小并发一上来线程全堵在拿连接那一步。改完配置再压,QPS 从原先卡在 200 出头直接抬到了 1600 上下。这类问题靠点点点是永远发现不了的。这篇内容我想围绕压力测试、QPS 以及 Locust 这个工具把我这些年做压测的整套思路和实操细节摊开讲。会讲清楚 QPS 到底指什么、它和 TPS、并发数、响应时间之间怎么换算会讲一套完整的压力测试方案怎么设计也会把 Locust 从装环境到写脚本、再到分布式压测和结果解读的全过程走一遍。文章里既有已经在做后端测试的同行可以直接抄的脚本也有刚接触性能测试、想知道接口压力测试怎么测的新人能看懂的基础解释。工具对比、参数计算、踩过的坑、排查套路我都会写进去尽量让你看完就能上手干。1. 先把 QPS 这件事彻底说明白1.1 QPS 到底指的是什么QPS全称 Queries Per Second每秒查询数。字面意思很简单一秒钟系统能处理多少个请求。但真正做过压测的人都知道这个数字如果脱离上下文单独拿出来说基本没意义。同样一个接口我在本机单进程压是 3000 QPS在四台压测机分布式压是 12000 QPS你说它到底是 3000 还是 12000所以谈 QPS 一定要带上四个坐标被测对象是谁、请求长什么样、测试环境什么规格、压测端有几台机器。很多人第一次接触这个词会下意识把它和“系统的能力上限”划等号其实不是。QPS 是某个特定条件下测出来的一个观测值它反映的是“在当前这个请求模型和资源配比下系统每秒能成功返回多少响应”。请求体大小、是否走缓存、是否命中索引、后端有没有下游依赖任何一个变量变了这个值都会跳。我做压测有个习惯出报告的时候永远把 QPS 和“当时的请求模型”绑在一起写不然过两周回头看自己都不记得那个数字是怎么来的。还有一个容易混淆的点QPS 统计的是“成功返回”的请求还是“发出去”的请求。这两个差别很大。一个接口如果 40% 的请求返回 500你把它算进 QPS 里数字看着很漂亮其实是自欺欺人。Locust 在统计时会区分总请求数和失败数RPS 那栏是包含失败在内的总吞吐真正要看的是成功请求的吞吐量这个后面讲结果解读的时候我还会专门说。1.2 QPS 和 TPS、并发数、响应时间到底怎么换算这是被问得最多的一个问题。先给结论在理想情况下QPS ≈ 并发数 ÷ 平均响应时间秒。举个数你开了 100 个并发用户接口平均响应时间是 50 毫秒也就是 0.05 秒那 QPS ≈ 100 ÷ 0.05 2000。这个公式来自排队论里的利特尔法则Littles Law原式是 并发数 吞吐量 × 响应时间变形一下就得到了上面那个。理解这个公式你就能明白两件特别重要的事。第一想提高 QPS要么加并发要么降响应时间而加并发往往是有限度的因为资源是有限的。第二当一个系统到达瓶颈后你继续加并发响应时间会等比上升QPS 却原地不动甚至往下掉——这就是性能测试里说的“拐点”。压测的核心目的之一就是找到这个拐点。那 TPS 又是什么TPSTransactions Per Second每秒事务数。它和 QPS 的区别在于“事务”的粒度。一个事务可能包含多个请求比如下单这个业务动作后台要调创建订单接口、扣库存接口、发消息接口三个请求合起来算一个事务。所以如果是单接口压测QPS 和 TPS 数值上基本重合如果是业务链路压测一个 TPS 可能对应好几个 QPS。做链路压测的时候我通常两个指标都盯TPS 反映业务真实处理能力QPS 反映底层接口的负载压力两者结合才能定位到瓶颈在哪一段。为了更直观我列个对照表把几个常被搞混的指标放在一起说明指标含义关注点常见误用QPS每秒查询数请求数接口层面的吞吐把失败请求也算成有效吞吐TPS每秒事务数业务动作层面的吞吐单接口压测时和 QPS 混着说并发数同时发起的请求数量系统并行处理压力把并发等同于线程数或用户数响应时间单请求从发出到返回的耗时用户体验只看平均值不看 P95、P99错误率失败请求占比系统可靠性压测时不设阈值只管冲高峰值1.3 为什么 QPS 这个指标最容易被看走眼说句实在话QPS 是性能指标里最容易“造”出来的。我见过一些团队为了让报告好看只压一个最简单的查询接口缓存全开然后把几十万的 QPS 写进文档。这种数字对实际系统没有任何指导意义因为真实流量是混合的有读有写有缓存命中也有击穿有同步调用也有异步回调。第二个容易看走眼的地方是平均值。平均响应时间 80 毫秒听起来很美但如果 P99 是 3 秒意味着每 100 个用户里就有 1 个要等三秒这在真实的用户感知里就是“偶尔卡顿”。压测的时候一定要让工具把分位数打出来Locust 会给你 50%、66%、75%、80%、90%、95%、98%、99% 这几个档位的响应时间看 P95 和 P99 比看平均值有价值得多。第三是“稳定”的假象。有些接口压 1 分钟很稳压 10 分钟就开始内存上涨、连接数堆积最后崩掉。这是资源泄漏的典型表现。所以我现在做压测短则 10 分钟长则跑几个小时专门观察 QPS 曲线是不是一条横线有没有缓慢下滑的趋势。只看 1 分钟的峰值很容易漏掉这类问题。2. 压力测试方案该怎么设计才不白测2.1 先明确目标你到底要回答什么问题压测不是“跑个工具看数字”它是要回答具体问题的。动手之前我会先跟业务和研发把目标对齐通常归成四类。第一类是容量摸底这套系统在现有配置下最多能扛多少 QPS超过多少会出问题。第二类是回归验证做了性能优化之后新版本比老版本快了多少。第三类是瓶颈定位知道系统扛不住但不知道卡在哪用压测去把瓶颈逼出来。第四类是稳定性验证系统能不能在某个压力水平下连续跑几个小时不出错。目标不一样做法完全不一样。容量摸底要阶梯式加压一级一级往上顶直到找到拐点回归验证要固定压力、固定时长保证两次测试条件一致唯一变量是代码版本瓶颈定位要配合监控把 CPU、内存、磁盘 IO、数据库指标全打开边压边看稳定性验证则要拉长时长重点看错误率和资源曲线。我见过太多人上来就闷头压压完一肚子数字不知道说明什么就是因为一开始没想清楚要回答什么。另外一定要提前确定验收标准。比如“接口 P95 响应时间不超过 200 毫秒、错误率低于 0.1%、QPS 不低于 5000”把这些写进测试计划里。有了这条线测完当场就能判断过没过而不是靠感觉。没有验收标准的压测最后往往变成一场“数字比大小”的表演。2.2 压测模型流量怎么造才像真实用户这是整个方案里技术含量最高、也最容易被敷衍的一环。压测模型说白了就是“我们要模拟什么样的用户行为”。一个真实的用户不会是每秒发一次请求的机器人他会有思考时间、会来回跳转、会集中访问某些热点数据、会有登录态和前置操作。如果你压测脚本就是无脑对同一个接口死循环那测出来的数据参考价值非常有限。我会从几个维度去还原真实流量。一是请求比例比如订单系统里查单、下单、取消的比例大概是 10:3:1那脚本里就用 Locust 的权重参数把这个比例配出来。二是参数分布不能所有请求都查同一个用户 ID要准备真实的参数池用 CSV 或数据库捞一批随机取用。三是思考时间Locust 支持在请求之间加随机的等待间隔模拟用户的停顿。四是前置状态比如很多接口需要登录 token得在脚本初始化时先登录拿到凭证。这里有个特别容易踩的坑如果所有虚拟用户都用同一个账号、同一个商品 ID压测时数据全落在同一行记录、同一个缓存 key 上测出来的性能会虚高。真实环境里数据是分散的锁竞争、缓存分布、索引命中的情况完全不同。我一般会准备几百到几千条参数让每个虚拟用户随机取尽量贴近真实分布。这一步做扎实了后面的结论才站得住。2.3 环境、数据、监控三件套的准备环境这块我的原则是“尽可能贴近生产但绝不在生产上乱来”。理想状态下压测环境要和生产的机器规格、网络拓扑、中间件配置保持一致但现实中很难完全做到那就退一步把关键差异记录下来在解读数据时心里有数。比如压测环境数据库是单机生产是主从集群那压出来的 QPS 只能作为下限参考不能直接当成生产容量。数据量级是另一个隐蔽的坑。一个表在有 100 万行数据时和有 100 行数据时查询性能天差地别。压测前一定要把测试库的数据量灌到和生产同一个量级尤其是那些会走索引、会做分页、会做聚合统计的接口。我见过压测时 QPS 很漂亮上线后慢查询一堆的情况十有八九就是测试库数据太少索引全在内存里什么查询都快。监控必须提前部署好而不是压到一半才想起来看。至少要有这几样被测服务的 CPU、内存、GC 情况数据库的连接数、慢查询、QPS中间件的队列长度、堆积情况压测机自身的负载。这里特别提醒一句压测机自己也可能成为瓶颈如果压测机的 CPU 跑满了、网络带宽打满了那你测出来的不是被测系统的上限而是压测机的上限。Locust 分布式压测就是为了解决这个问题后面会详细讲怎么搭。3. 工具选型为什么我最后常驻 Locust3.1 主流压测工具横向对比市面上的压测工具挺多我这些年用下来比较有代表性的有这几个JMeter、Locust、wrk、ab加上云厂商提供的托管压测服务。每个都有它的适用场景没有绝对的好坏关键看你的需求和团队情况。工具脚本方式协议扩展分布式上手难度适合场景JMeterGUI XML 配置插件丰富原生支持中等复杂业务流程、需要图形化编排Locust纯 Python 代码写代码扩展原生支持中低会 Python 即可接口压测、需要灵活逻辑wrk命令行 Lua需写 Lua不原生较高单接口极限压测追求高并发ab命令行几乎不支持不支持低快速验证单个接口托管压测服务平台配置视平台而定平台负责低临时大流量压测、不想维护机器JMeter 的教程网上一抓一大把它对不写代码的同学很友好图形界面点一点就能搭出流程。缺点是脚本是 XML团队协作和版本管理比较别扭复杂逻辑要靠各种组件堆久了容易变成一坨“意大利面”。wrk 和 ab 适合单接口的快速极限压测但业务逻辑一复杂就力不从心——比如要先登录、要参数化、要按比例混合多个请求它们就不好使了。Locust 打动我的地方在于它是“代码即脚本”。你用一个 Python 类描述用户行为用装饰器标注任务和权重整个逻辑清晰、可读、可复用、可进 Git。对于已经有 Python 基础的团队学习成本非常低而且你想加什么逻辑就加什么逻辑——随机参数、条件分支、自定义断言、串联多个接口全都能写。这种灵活性是图形化工具比不了的。3.2 Locust 的架构优势在哪Locust 的架构有个很聪明的设计它是基于事件驱动的用的是 gevent 这类协程库。这意味着单个进程里可以用很少的系统开销模拟出大量并发用户。传统的一个线程模拟一个用户几千并发就要几千个线程上下文切换的代价非常大Locust 用协程把这个问题绕开了一台普通机器单进程跑几千并发不是难事。它的另一个优势是分布式扩展特别顺。主节点master负责分发任务和汇总统计工作节点worker负责实际施压加机器就是线性加压力。你只要在被压的机器上多起几个 worker或者在压测机上用多进程模式把多核吃满压力就能成倍往上走。这套分发机制配置起来就几个命令行参数的事比很多工具都省心。还有一点是实时 Web UI。Locust 默认起一个网页界面压测过程中你能实时看到 QPS 曲线、响应时间分布、失败请求列表还能动态调整并发用户数比赛式地往上加边加边观察拐点在哪。对于做容量摸底这种需要“慢慢顶上去”的场景这个交互体验非常好。当然正式回归测试时我会关掉 Web UI用无头模式--headless跑方便集成到自动化流程里。3.3 哪些场景 Locust 并不适合得客观说Locust 不是万能的。第一它天生偏重 HTTP/HTTPS 这类协议虽然有扩展机制可以支持别的协议但你要压 gRPC、WebSocket 或者自定义的二进制协议就得自己写客户端封装工作量不小这种时候专业的协议压测工具更合适。第二Locust 的绝对性能上限不如 wrk 这种 C 语言写的工具。它的优势是灵活和易用追求极致单机吞吐的场景wrk 更有竞争力。不过实际压测里我们更多是分布式压单机的那点差距可以通过加 worker 补回来。第三如果团队里完全没有 Python 基础且只是做很标准的单接口压测那用图形化工具或者托管服务可能更快出结果。工具是为目标服务的没必要为了“显得专业”去选一个自己不熟的东西。我的建议是需要复杂业务逻辑、需要灵活参数化、团队有 Python 底子的选 Locust纯粹压一个接口看极限的wrk 或 ab 更省事。4. Locust 实操从零把一个接口压起来4.1 环境搭建与安装Locust 是 Python 写的所以第一步是把 Python 环境准备好。我用的是 Python 3.8 以上的版本装起来就一条命令pip install locust装完之后验证一下locust --version能打印出版本号就说明 OK 了。这里提醒一句最好用虚拟环境隔离避免和系统里其他库冲突python -m venv locust-env source locust-env/bin/activate pip install locustWindows 下激活命令是locust-env\Scripts\activate其他步骤一样。装好之后不用急着写脚本Locust 自带一个官方示例可以直接拿来跑通感受一下它的工作方式。4.2 第一个压测脚本长什么样Locust 的核心就是定义一个继承自HttpUser的类类里的方法用task装饰器标注方法体里写你要发的请求。下面是我常用的一个模板包含了登录拿 token、按权重混合多个请求、加思考时间这几个关键要素from locust import HttpUser, task, between class OrderUser(HttpUser): # 每个虚拟用户两次请求之间随机等待 1 到 3 秒 wait_time between(1, 3) def on_start(self): # 每个虚拟用户启动时先登录拿到 token resp self.client.post( /api/login, json{username: test_user, password: test_pass}, ) self.token resp.json().get(token, ) task(3) def query_order(self): # 权重 3查单逻辑占大头 self.client.get( /api/order/list, headers{Authorization: self.token}, name订单列表, ) task(1) def create_order(self): # 权重 1下单逻辑 self.client.post( /api/order/create, headers{Authorization: self.token}, json{skuId: 10001, count: 1}, name创建订单, )几个点解释一下。wait_time控制思考时间不加的话虚拟用户会疯狂空转压力会失真。on_start会在每个虚拟用户启动时执行一次适合做登录这类前置操作。task(3)里的数字是权重表示这个任务被选中的相对概率上面例子中查单和下单的调用比例大概是 3:1。每个请求都加了name参数这是为了在报表里把同类请求聚合展示否则带不同参数的 URL 会被拆成很多行看报表很乱。4.3 参数化别让所有请求打在同一条数据上前面说过参数化是压测质量的关键。Locust 里最常用的做法是读 CSV然后随机取行。示例import csv import random from locust import HttpUser, task, between class ParamUser(HttpUser): wait_time between(0.5, 2) def on_start(self): # 从文件里加载参数池 with open(params.csv, newline, encodingutf-8) as f: self.params list(csv.DictReader(f)) task def query_detail(self): p random.choice(self.params) self.client.get( f/api/product/{p[sku_id]}, name商品详情, )params.csv里就放真实的sku_id、用户 ID 这类字段几百上千条起步。这样每个虚拟用户取到的参数都不一样能更真实地模拟数据分散访问的情况。如果参数池太小比如只有十条那本质上还是在压同几条记录测出来的数据会偏乐观。参数池的大小我的经验是至少覆盖你能想到的热点数据边界有条件的话直接从一个脱敏的生产数据副本里捞。4.4 分布式压测怎么把压力顶上去单机压不动的时候就该上分布式了。Locust 的分布式很直白一台机起 master其余机起 workerworker 连上 master 后由 master 统一发号施令。先在一台机器上启动主节点locust -f locustfile.py --master --web-port 8089然后在其他机器上启动工作节点指向主节点的地址locust -f locustfile.py --worker --master-host192.168.1.100启动成功后在 master 的 Web UI 里设置并发用户数任务会自动分摊到各个 worker 上统计结果也由 master 汇总。加 worker 的过程可以随时进行压力不够就多挂几台。这里有个很多新手不知道的甜点如果压测机是多核的其实不用多台机器单机上也可以把多个核吃满。用--processes参数指定进程数即可locust -f locustfile.py --headless -u 2000 -r 100 --processes 8-u 2000是目标并发用户数-r 100是每秒启动多少用户爬坡速率--processes 8表示起 8 个进程并行施压一般设置成 CPU 核数或多一点。这种方式省去搭多机的麻烦性价比很高我在压测机有 8 核以上的时候基本都用它。前提是压测机本身别成为瓶颈压的时候盯一下它的 CPU 和网络。4.5 Web UI 和无头模式的用法与选择Web UI 模式适合探索性压测。访问http://压测机IP:8089填上并发数和爬坡速率就能开跑。界面上几块核心信息要会看Statistics 里是各接口的请求数、失败数、响应时间分位数和 RPSCharts 里是实时曲线Failures 里能看到失败请求的具体报错。我一般会用它做“阶梯加压”——先设 100 并发跑两分钟看曲线没问题就加到 200、500、1000一级一级观察 QPS 和响应时间的变化拐点往往就在某次加压后突然出现。正式测试或者要集成到 CI 里时我会用无头模式locust -f locustfile.py --headless -u 500 -r 50 -t 10m --csvresult --htmlreport.html-t 10m表示跑 10 分钟--csv导出原始数据--html生成一份可直接发给同事的报告。这套命令适合放进自动化脚本里定时跑做版本间的性能回归对比。结果解读的时候重点盯三样东西成功请求的 RPS、P95/P99 响应时间、错误率。这三个指标任何一个掉链子都说明这次压测发现了问题。5. 常见问题与排查技巧实录5.1 压力死活压不上去的几种典型原因压测时最常见的困惑就是并发数加了QPS 却一动不动。这时候别急着怀疑被测系统先按顺序排查。第一件事是看压测机自身的负载。用top看一下 CPU 使用率sar -n DEV 1看一下网卡流量。如果压测机 CPU 已经跑满或者网卡已经打满那你测的是压测机的上限不是系统的上限。解决办法是加 worker 或者加机器把压力分散开。第二件事是检查客户端的连接数限制。Linux 默认单进程能打开的文件描述符有限高并发时会出现“Too many open files”的报错。临时调高的话ulimit -n 65535永久生效要改/etc/security/limits.conf。这个坑我踩过好几次压到几千并发就莫名其妙报错查半天才发现是文件描述符到了上限。第三件事是端口耗尽。客户端每次发请求都要占一个本地端口端口范围是有限的短连接压测时端口回收不过来就会连接失败。可以调大本地端口范围或者改用长连接。Locust 的HttpUser默认会复用连接如果你用的是自定义客户端注意别每次请求都新建连接。5.2 结果数据自相矛盾的排查思路还有一种情况是数据看着不对劲。比如 QPS 显示很高但后端监控里数据库的请求量根本对不上。这时候先看是不是压测机到服务端之间走了缓存或者 CDN请求根本没打到后端。再比如平均响应时间很短但 P99 特别长说明有少量请求卡住了可能是偶发的锁等待、GC 停顿或者慢查询要结合后端日志去挖。失败率高但报错信息含糊的我一般先用小并发复现。50 并发跑一遍如果也报错观察具体的前几个失败请求的完整响应体往往能直接找到原因比如 401 未授权token 逻辑有问题、429 限流触发了网关限流、500后端异常。Locust 的 Failures 页面会分组展示失败原因善用这个功能能省很多事。另外记得区分“服务端错误”和“压测端主动超时”前者是真问题后者可能是你设置的超时时间太短把正常但稍慢的响应也算成失败了。5.3 常见问题速查表把压测过程中高频出现的问题和应对方式整理成一张表方便现场对照现象可能原因排查手段处理方式QPS 上不去压测机负载高压测机成为瓶颈看压测机 CPU、网卡加 worker、加机器、调--processes报 Too many open files文件描述符不足查ulimit -n调高系统限制大量连接失败本地端口耗尽查连接状态统计调大端口范围、改用长连接失败率高含 429触发限流看响应体和网关日志调整压测速率或申请放开限流平均响应快但 P99 高偶发慢请求看后端慢查询、GC 日志定位个例优化慢路径跑久了 QPS 缓慢下滑资源泄漏或连接堆积观察内存、连接数曲线排查泄漏点检查连接回收数据库请求量对不上走了缓存或中间层看缓存命中、网关转发确认请求真实到达后端注意压测前一定和生产、运维确认好限流、熔断、告警策略。很多系统对异常流量有自动防护压测很容易触发导致结果失真甚至影响线上务必提前打个招呼把相关策略临时调整或加白名单。6. 换个视角模型调用与各类资源压测6.1 模型调用场景下 QPS 有什么不一样现在越来越多系统后面接的是模型服务这时候 QPS 的含义和传统接口就有些区别了。传统接口的 QPS 和响应时间关系比较线性你加并发响应时间缓慢上升到瓶颈才陡增。而模型推理服务的响应时间往往很长几百毫秒到几秒不等而且波动大受输入长度、生成长度、批次调度策略影响明显。这时候谈 QPS 就必须带上“在什么输入长度、什么输出长度下”的前提否则数字毫无可比性。模型服务的 QPS 优化思路也和普通接口不同。普通接口靠加缓存、优化 SQL 能立竿见影模型服务更多是走批处理、动态 batching、算子优化、量化加速这些路子。压测这类服务时我特别关注两个额外指标单请求的排队等待时间以及不同并发下吞吐量的变化曲线。很多时候并发加到某个点后吞吐不再上升延迟却急剧增加这说明推理资源已经饱和再堆并发只会让所有请求一起变慢用户体验反而更差。还有一点模型服务的压测请求内容本身要贴近真实。输入是一句话还是几千字结果差别巨大。如果压测只发很短的空输入测出来的 QPS 会好看得离谱完全没有参考价值。所以压测数据要么从真实请求采样要么根据业务场景构造出合理的长度分布。6.2 接口压测、CPU、GPU、存储压测的区别需要说明的是日常说的“压力测试”覆盖面很广接口和模型服务压测只是其中一类。系统层面的压力测试还包括对 CPU、GPU、存储等硬件资源的专项压测目的各不相同别混为一谈。CPU 压测主要是为验证散热和调度能力。常见的做法是用stress-ng或专门的烤机工具把指定核心的负载打满观察温度、频率是否稳定、有没有降频。这类测试在装机、服务器上架前做得多。GPU 压测也是类似思路用专门的 GPU 负载工具让显卡持续高负载运行检查散热和显存是否有异常常用于新卡上机或者驱动验证。这些属于硬件层面的可靠性验证和接口 QPS 不是一回事但都会影响服务最终能承受的压力。存储压测关注的是磁盘的读写能力。比较常用的方法是用dd或者更专业的文件系统压测工具对某个目录持续写入和读取测量顺序读写、随机读写的带宽和 IOPS。比如对应用的数据盘做一次写测试看写入速度是否符合预期是否存在掉速。这和接口压测的关系在于如果你的接口涉及大量文件读写或者日志落盘磁盘的 IO 能力很可能是隐藏瓶颈接口压测时 QPS 上不去最后追查发现是磁盘 IO 打满了。所以完整的压测方案往往需要接口层和资源层结合着看才能定位到真正的问题所在。我自己的习惯是接口压测的同时把所有资源指标都挂在监控大屏上哪个资源先顶到天花板瓶颈就在哪。CPU 先满说明计算密集内存先满可能有泄漏或者缓存过大磁盘 IO 先满检查日志和文件操作网络先满考虑是不是响应体太大。把这几个维度和接口 QPS 放在一起看排查效率会高很多。压力测试从来不是跑一个工具就完事它是一套结合了目标设定、模型设计、资源观测和结果分析的完整方法Locust 只是这套方法里顺手的一把趁手工具。最后再分享一个我自己的小习惯每次压测结束不管结论好坏我都会把脚本、参数文件、环境说明和结果报告一起归档到一个目录里命名带上日期和版本号。过段时间要做性能回归直接翻出来照着跑条件一致对比才有意义。踩过的坑大多记在这些报告里下次遇到类似的现象翻一翻往往就能少走弯路。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →