假设你新开了一家主打 AI 特色菜的餐厅,开业前最怕的是客流突然涌进来,后厨和点餐系统一起宕机。于是你请来三支服务团队做“营业压力预演”:JMeter 是资历最老的宴会厅,菜单厚、设备多,什么流程都能录;Locust 是自由潇洒的移动餐车,主厨用 Python 写顾客动线,轻便好改;k6 是自带大屏幕的自动化快闪店,用 JavaScript 描述点单场景,天然和监控仪表盘长在一起。三支团队都能回答同一个问题:餐厅每小时最多能接多少桌、哪道菜最容易把后厨堵死。本文就用这三支团队的同台演练,讲清压测工具选型、脚本编写、协议支持、并发模型、报告输出与容量规划。
学会给 AI 服务“提前制造客流”,你才能在设计阶段发现容量天花板,而不是等线上告警把深夜惊醒。
可视化演示
展示JMeter、Locust、k6三大压力测试工具的工作原理,模拟并发请求负载生成与系统性能指标采集过程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
压力测试工具 负载生成:模拟并发用户请求 指标采集:QPS、延迟、错误率 JMeter/Locust/k6:主流压测工具对比 Users → Requests → SUT → Metrics 压力测试工具生成负载并采集性能指标
三大工具全家福
你给餐厅做营业压力预演时,三支队伍长相完全不同。宴会厅 JMeter 是 Apache 基金会的“老法师”,打开界面就是树状菜单、线程组、采样器、监听器,像一张巨大的固定菜单,能容纳非常多的业务流程。移动餐车 Locust 的看家本领是把“顾客行为”写成 Python 代码,主厨想改哪一道菜就直接改代码,改完立刻能重新出摊。自动化快闪店 k6 则用 JavaScript 描述点单流程,后厨是 Go 语言打造的流水线,既能快速接单,又能把数据实时投到监控大屏上。
三支队伍虽然风格不同,但服务的都是同一个目标:用可控的并发流量试探系统的“接待上限”。做压测时,我们关心的不是“一共点了多少桌”,而是“每秒能处理多少笔点单”,这就是吞吐量。于是有了最常用的性能指标公式:
回到餐厅比喻:无论你请宴会厅、餐车还是快闪店,最终要回答的都是“每秒能接多少单、每单要等多久、会不会把某道工序堵死”。先认识三支队伍擅长什么,再谈选哪个,才不会在开业当天手忙脚乱。
- 请求数 N压测过程中客户端发出的完整业务请求总量,例如 6000 次点单。
- 时间窗口 T统计这些请求所用的秒数,例如 60 秒。
- QPS 结果两者相除得到每秒处理能力,例如 6000 除以 60 就是 100 QPS。
脚本编写方式
宴会厅的服务员习惯用固定点菜器:经理在 JMeter 图形界面上拖拽线程组、添加 HTTP 采样器、配置断言,最后保存成一个 .jmx 文件。这个文件本质上是 XML,记录了一整棵“组件树”。优点是测试同学不用写代码,鼠标点一点就能跑起来;缺点是文件一复杂,团队协作和版本管理就会头疼。
移动餐车的主厨更喜欢写 Python。Locust 的核心脚本是一个普通 Python 文件,你定义一个继承 HttpUser 的类,用 @task 装饰器标记哪些操作是顾客高频动作。因为本质是代码,所以可以很方便地接入 Git、Code Review 和 CI/CD。
自动化快闪店则用 JavaScript 写场景,k6 的脚本结构非常简洁:import http from 'k6/http' 引入客户端,在 default 函数里写请求行为,再用 options 声明并发数。写起来像写接口测试,读起来也像一份点单流程图。
简单对比三者的脚本方式:JMeter 是“可视化表单 + XML”,适合不喜欢写代码的团队;Locust 是“Python 类与装饰器”,适合后端 Python 开发者;k6 是“JavaScript 函数”,适合前端或全栈工程师。选哪支服务团队,本质上是在选“你们厨房里谁最会读这份菜谱”。
协议支持对比
一家餐厅要服务不同口味的客人:有人用微信扫码点餐,有人直接付现金,还有人用会员卡积分。压测工具也一样,至少要支持被测系统使用的“点餐方式”,也就是网络协议。
JMeter 的开箱协议覆盖最广,HTTP、HTTPS、FTP、JDBC、JMS 都可以通过采样器直接压测。它还可以通过插件扩展更多协议,就像宴会厅准备了从筷子到刀叉的全套餐具。如果企业里有大量历史系统用的是数据库连接、消息队列或老接口,JMeter 常常是最稳妥的选择。
Locust 默认只对 HTTP/HTTPS 提供开箱即用的 HttpUser,但因为是 Python 写的,你可以写自定义客户端来压测 WebSocket、gRPC 或私有 TCP 协议。这有点像移动餐车只带基础厨具,但主厨手艺好,想做什么都能临时搭个灶台。
k6 更聚焦现代 Web 和云原生环境,原生支持 HTTP/1.1、HTTP/2、WebSocket 和 gRPC,和 Kubernetes、微服务架构配合得很顺。快闪店菜单虽然不长,但每道菜都对应现今年轻人最喜欢的口味。选工具前先盘一盘自己的“点餐方式”,协议支持边界就是压测工具的边界。
并发模型与扩展
宴会厅接客的方式很“实在”:每来一桌客人,就安排一个服务员全程跟桌。JMeter 也是这样,一个线程就是一个虚拟用户,线程越多,占用的 JVM 内存越多。假如你想模拟 5000 个并发用户,往往需要启动分布式模式,让多台压测机一起“加桌子”。
移动餐车接客的方式更聪明:一个主厨可以同时照看很多口锅,不用每口锅配一个厨师。Locust 使用 gevent 协程,把大量“等待网络响应”的时间省出来,因此单台机器能模拟的用户数通常比 JMeter 高。不过它跑在 Python 进程里,遇到 CPU 密集型任务时依然受到 GIL 的限制。
k6 的快闪店把出餐流程做成了 Go 的并发流水线,goroutine 非常轻量,单台机器就能撑起很高的虚拟用户数。三种工具的并发模型差异,可以用 Little's Law 来理解:
在团队规模不大、只想快速验证服务上限时,协程和 goroutine 明显更省资源;但如果压测场景复杂、需要兼容大量老协议,宴会厅“人海战术”也有它的安全边界。理解并发模型,你就知道为什么“同样 1000 并发”,不同工具对压测机资源的要求截然不同。
- 请求数 n压测时间段内系统完成的请求总量,例如 1200 个请求。
- 平均响应时间 L每个请求从发出到收到响应平均花了 0.5 秒。
- 总时长 T统计这些请求所用的 60 秒。
- 估算并发 C把三者代入后得到 10,表示这组吞吐与延迟背后大约对应 10 个并发用户。
报告与监控
营业预演结束,不能只说“今天接待了很多人”,你还要知道哪条队伍排最长、哪个环节最慢。三支服务团队给出的账单也不一样。
JMeter 最经典的是聚合报告,里面展示每个接口的样本数、平均响应时间、中位数、错误率和吞吐量。你还可以用命令行执行后用 -e -o 生成一份静态 HTML 报告,像宴会厅给你打印一本厚厚的营业报表。
Locust 自带一个 Web 页面,压测过程中实时刷新请求数、响应时间、失败率,还能看到每秒并发用户的曲线。厨师在浏览器里就能看到出餐速度是不是稳。
k6 最擅长和 Grafana 生态联动。它会把指标流式输出到 Prometheus、InfluxDB,或直接对接 Grafana 数据源,天生就适合挂在可视化大屏上。快闪店不仅告诉你结果,还把你带到监控室看实时大盘。
报告的价值不是“好看”,而是帮你定位瓶颈:如果 QPS 曲线一直是平的但延迟在涨,可能说明资源已经打满;如果错误率在某个并发点突然升高,可能说明限流或连接池不够。无论选哪支团队,都要让它把“过程数据”而不是“最终结论”完整交给你。
场景应用选型
周末家庭宴请,你会选宴会厅;街头做快闪活动,你会选移动餐车;科技园区的外卖快闪店,你可能会选自动化 k6。选压测工具不是比参数,而是比“匹配度”。
首先是团队语言:团队熟悉 Python,选 Locust 的维护成本最低;团队熟悉 JavaScript,k6 的脚本几乎没有心智负担;团队主要是测试工程师且不写代码,JMeter 的 GUI 最容易上手。其次是被测系统:老系统用 JDBC、JMS 或 FTP,JMeter 最从容;云原生微服务用 HTTP/2、gRPC,k6 最顺手;混合链路且需要快速自定义协议,Locust 的 Python 扩展最灵活。
最后是压测规模与报告生态:需要分布式压测时,JMeter 主从模式成熟、Locust master/worker 简单、k6 也有云服务和 Kubernetes Operator 可以扩展。需要给老板看大屏,k6 + Grafana 最直观;需要导出 Excel 报表,JMeter 的传统监听器更符合测试团队习惯。
选型之后就要学会定位瓶颈:先用小并发暖场,再阶梯式加压,观察 QPS 增长是否线性、延迟是否突变、错误率是否抬头。正如你不会让宴会厅和快闪店在完全相同的时刻接待同样多的客人,每次压测都要设计好场景数据、用户行为和监控指标。选对工具,只是这场容量规划演练的开始。
---
AI 应用场景
AI 模型推理服务容量规划
阶段:容量评估与上线前压测
解决的问题:AI 推理服务的响应时间受并发、显存、排队机制影响,很难靠直觉估算最大承载量。传统做法是上线后被动观察监控,容易在流量高峰时出现超时和雪崩。用 k6 对模型网关做阶梯加压,可以提前画出 QPS 与延迟曲线,找出服务开始“跪”的临界点。
具体案例:某团队上线一个 OpenAI 兼容的翻译模型接口,用 k6 编写 JavaScript 脚本,从 10 并发逐步加到 200 并发,同时观察首字延迟和 P95 响应时间。最终发现在 80 并发时延迟显著抬升,于是把网关限流值设为 60,保障了高峰期大多数请求的体验。
RAG 检索链路压测
阶段:全链路压测与瓶颈定位
解决的问题:RAG 应用涉及向量数据库、大模型调用、前置检索多个环节,单独压一个服务看不出整条链路的上限。用 Locust 写 Python 脚本,可以模拟用户提问并穿透到向量库和 LLM 网关,快速定位是检索慢、生成慢还是并发排队慢。
具体案例:一个知识库问答系统在压测中发现 QPS 很低,通过 Locust 分别压测检索服务和生成服务,发现向量索引召回耗时占 70%。团队优化索引后,整体 P95 延迟下降了一半。
大促前全链路压测
阶段:高峰保障与容量规划
解决的问题:电商大促前,登录、商品、订单多个系统会同时承受流量,传统压测只压单接口无法还原真实场景。JMeter 通过多线程组和混合比例采样器,可以模拟不同接口的真实流量比例,适合传统企业对稳定性和合规性的严格要求。
具体案例:某电商平台用 JMeter 组织分布式压测,几百台压测机同时发起请求,压出下单接口的容量上限后,把结果反馈给服务端做限流和扩容。
Python 演示
import timeimport urllib.requestfrom concurrent.futures import ThreadPoolExecutor class MiniLoadTester: """一个 30 行的迷你压测器, 用来体会 Locust 的并发思想.""" def __init__(self, url, concurrency=20, duration=10): self.url = url self.concurrency = concurrency self.duration = duration self.success_count = 0 self.error_count = 0 def _send(self, _): start = time.perf_counter() try: urllib.request.urlopen(self.url, timeout=5) self.success_count += 1 except Exception: self.error_count += 1 return time.perf_counter() - start def run(self): deadline = time.time() + self.duration latencies = [] with ThreadPoolExecutor(max_workers=self.concurrency) as pool: while time.time() < deadline: futures = [ pool.submit(self._send, i) for i in range(self.concurrency) ] for future in futures: latencies.append(future.result()) total = self.success_count + self.error_count qps = self.success_count / self.duration avg_latency = sum(latencies) / max(len(latencies), 1) return { "total": total, "success": self.success_count, "errors": self.error_count, "qps": round(qps, 2), "avg_latency": round(avg_latency, 4), } if __name__ == "__main__": result = MiniLoadTester( "http://127.0.0.1:8000/health", concurrency=20, duration=5 ).run() print(result)
ThreadPoolExecutor(max_workers=self.concurrency)同时安排 n 个“服务员”并行发请求,模拟并发用户。pool.submit(self._send, i)把每个请求任务投递给空闲线程,类似用户进店后立刻开台。futures = [...]收集这一批请求的 Future 对象,表示所有任务都已派发。for future in futures: latencies.append(future.result())等服务团队全部回单,再统计每笔耗时。qps = self.success_count / self.duration串联第 1 节的 QPS 公式,用成功数除以压测时长。avg_latency = sum(latencies) / max(len(latencies), 1)计算平均响应时间,判断服务是快还是慢。
常见误区
误区压测当然要把并发调到 10000,数字越大越真实,调小了测不出问题。
正解模拟客流必须贴近真实到店曲线。并发数只是手段,真实业务既有高峰又有低谷;盲目高并发可能直接击穿限流阀,结果全是错误码而不是性能数据。正确做法是先做小规模探底,再阶梯加压观察拐点。
为何常见:新手容易把“压得狠”和“测得准”画等号,忽略了场景真实性和指标有效性。
误区换工具就是把脚本重写一遍,其他都一样,反正都是发 HTTP 请求。
正解换工具意味着并发模型、协议适配、监控链路和团队技能一起换。JMeter 的多协议插件与 GUI 管理适合老系统,k6 的 JS 脚本和 Grafana 生态更适合云原生 CI/CD。只看“能发请求”就替换,很容易在分布式压测和报告集成上踩坑。
为何常见:人们习惯用“底层都是 HTTP”简化复杂问题,忽略了工具链和团队协作方式的差异。
误区压测报告显示 QPS 达到 5000,这个系统一定很强,可以直接上线。
正解QPS 高可能是压测脚本太短、没有校验返回内容,错误请求也在计数。真正要看的是错误率、P95/P99 延迟、资源水位和响应超时比例,QPS 只是其中一个数字。
为何常见:QPS 是最容易理解也最容易传播的指标,于是被误当成唯一标准。
误区一台压测机不够,就上十台,并发一定也能涨十倍。
正解分布式是扩大“客户端发压能力”,不是扩大系统的处理能力。如果压测机之间时钟不同步、结果不聚合,数据会非常混乱。需要先确认瓶颈在服务端还是压测端,再决定是否扩容。
为何常见:看到“分布式”三个字就以为是万能解药,忽略了压测架构本身的协调成本。
面试题
Q1. JMeter 生成的可执行脚本通常以什么格式保存?
- A.XML 文件保存组件树← 正确答案
- B.JSON 文件保存采样
- C.YAML 文件保存场景
- D.Python 文件保存逻辑
答案:A。JMeter 将线程组、采样器、监听器等组件序列化为 .jmx 文件,本质是 XML。JSON、YAML、Python 分别是其他工具的脚本形式,不是 JMeter 的默认存储格式。
Q2. Locust 用哪种方式支持高并发用户?
- A.每个用户一个进程
- B.gevent 协程调度← 正确答案
- C.浏览器多标签页
- D.Java 线程池并发
答案:B。Locust 使用 gevent 协程模拟大量用户,而不是为每个用户分配操作系统线程。多进程不是 Locust 的默认单位,浏览器和 Java 线程池也与 Locust 无关。
Q3. k6 的测试场景由什么语言编写?
- A.JavaScript ES6 脚本← 正确答案
- B.Python 脚本和装饰器
- C.Go 原生编译代码
- D.XML 图形化组件
答案:A。k6 的脚本语言是 JavaScript,虽然它的运行引擎用 Go 实现,但用户不需要写 Go。Python 是 Locust 的语言,XML 是 JMeter 的格式。
Q4. 相比 JMeter 线程模型,Locust 协程模型最大特点是什么?
- A.每个用户独占一个核心
- B.用轻量协程降低等待← 正确答案
- C.无法处理网络 I/O 请求
- D.必须关闭 JVM 才能运行
答案:B。协程在等待 I/O 时主动让出控制权,能在一个线程上处理大量连接,因此 Locust 更省资源。它并不是独占 CPU 核心,也能很好地处理网络 I/O,更不需要关闭 JVM。
Q5. 实时观察压测的 QPS 与延迟,哪种做法最合理?
- A.等待压测结束看聚合报表
- B.压测期间不做任何监控
- C.接入 Prometheus 和 Grafana← 正确答案
- D.从日志 grep 错误信息
答案:C。实时监控能看出压测过程中曲线何时开始恶化,而只看最终报表会丢失拐点信息。k6 与 Grafana 生态集成可以实时呈现吞吐、延迟和错误率,方便定位瓶颈。
Q6. Little's Law 中 C = nL/T,其中 L 代表什么?
- A.平均响应时间← 正确答案
- B.每秒请求数量
- C.单次任务请求数
- D.压测运行总时长
答案:A。L 是平均响应时间,T 是统计时长,n 是完成请求数。C = nL/T 用来估算并发用户数。每秒请求数是 QPS,单次任务请求数与公式无关。
Q7. 理论 QPS 1000,500 并发只跑到 600,最合理的推断是什么?
- A.系统可能已接近当前容量上限← 正确答案
- B.应立刻切换另一个压测工具
- C.500 并发一定还远远不够
- D.调大并发后性能必然线性上升
答案:A。理论值通常是理想上限,实测 600 很可能说明瓶颈已经出现。换工具不能解决容量问题,线性上升也不成立,需要继续做资源分析和瓶颈定位。
Q8. 压测定位 AI 推理服务瓶颈时应采集哪些指标?
- A.首字延迟与 P95/P99 分位← 正确答案
- B.GPU 显存利用与排队情况← 正确答案
- C.训练集大小与标注准确率
- D.模型参数量与层数设计
答案:AB。容量规划关注的是服务在并发下的响应时间和资源排队情况,因此首字延迟、分位延迟、GPU 显存和排队长度是关键。训练集、标注准确率、参数量和层数是模型训练阶段关心的指标。
Q9. 相比 JMeter,k6 更受云原生团队欢迎的原因有哪些?
- A.脚本使用 JavaScript 编写← 正确答案
- B.轻量级且资源占用低← 正确答案
- C.原生对接 Grafana 生态← 正确答案
- D.自带 Windows 桌面 GUI
答案:ABC。k6 用 JS 写脚本、用 Go 并发实现轻量高并发,并且与 Grafana 生态结合紧密。它没有传统意义上的 Windows 桌面 GUI,因此 D 错误。
Q10. 压测选型需要综合考虑哪些因素?
- A.团队熟练使用的语言← 正确答案
- B.被测系统的网络协议← 正确答案
- C.是否需要分布式压测← 正确答案
- D.压测机所在机房楼层
答案:ABC。团队语言影响脚本维护成本,被测协议决定工具边界,分布式能力决定压测规模上限。机房楼层属于物理位置,不是压测工具选型的核心依据。