假设你新开了一家主打 AI 特色菜的餐厅,开业前最怕的是客流突然涌进来,后厨和点餐系统一起宕机。于是你请来三支服务团队做“营业压力预演”:JMeter 是资历最老的宴会厅,菜单厚、设备多,什么流程都能录;Locust 是自由潇洒的移动餐车,主厨用 Python 写顾客动线,轻便好改;k6 是自带大屏幕的自动化快闪店,用 JavaScript 描述点单场景,天然和监控仪表盘长在一起。三支团队都能回答同一个问题:餐厅每小时最多能接多少桌、哪道菜最容易把后厨堵死。本文就用这三支团队的同台演练,讲清压测工具选型、脚本编写、协议支持、并发模型、报告输出与容量规划。

岗位关联

学会给 AI 服务“提前制造客流”,你才能在设计阶段发现容量天花板,而不是等线上告警把深夜惊醒。

目录
  1. 可视化演示
  2. 三大工具全家福
  3. 脚本编写方式
  4. 协议支持对比
  5. 并发模型与扩展
  6. 报告与监控
  7. 场景应用选型
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示JMeter、Locust、k6三大压力测试工具的工作原理,模拟并发请求负载生成与系统性能指标采集过程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

压力测试工具 负载生成:模拟并发用户请求 指标采集:QPS、延迟、错误率 JMeter/Locust/k6:主流压测工具对比 Users → Requests → SUT → Metrics 压力测试工具生成负载并采集性能指标

三大工具全家福

你给餐厅做营业压力预演时,三支队伍长相完全不同。宴会厅 JMeter 是 Apache 基金会的“老法师”,打开界面就是树状菜单、线程组、采样器、监听器,像一张巨大的固定菜单,能容纳非常多的业务流程。移动餐车 Locust 的看家本领是把“顾客行为”写成 Python 代码,主厨想改哪一道菜就直接改代码,改完立刻能重新出摊。自动化快闪店 k6 则用 JavaScript 描述点单流程,后厨是 Go 语言打造的流水线,既能快速接单,又能把数据实时投到监控大屏上。

三支队伍虽然风格不同,但服务的都是同一个目标:用可控的并发流量试探系统的“接待上限”。做压测时,我们关心的不是“一共点了多少桌”,而是“每秒能处理多少笔点单”,这就是吞吐量。于是有了最常用的性能指标公式:

回到餐厅比喻:无论你请宴会厅、餐车还是快闪店,最终要回答的都是“每秒能接多少单、每单要等多久、会不会把某道工序堵死”。先认识三支队伍擅长什么,再谈选哪个,才不会在开业当天手忙脚乱。

QPS=NT\displaystyle \text{QPS} = \frac{N}{T}
QPS 表示每秒成功处理的请求数,N 是请求总数,T 是压测所花费的秒数。
  1. 请求数 N压测过程中客户端发出的完整业务请求总量,例如 6000 次点单。
    N=6000\displaystyle N = 6000
  2. 时间窗口 T统计这些请求所用的秒数,例如 60 秒。
    T=60\displaystyle T = 60
  3. QPS 结果两者相除得到每秒处理能力,例如 6000 除以 60 就是 100 QPS。
    QPS=600060=100\displaystyle \text{QPS} = \frac{6000}{60} = 100

脚本编写方式

宴会厅的服务员习惯用固定点菜器:经理在 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 并发”,不同工具对压测机资源的要求截然不同。

C=n×LT\displaystyle C = \frac{n \times L}{T}
C 是估算出的并发用户数,n 是压测期间完成的请求数,L 是平均响应时间,T 是总时长。
  1. 请求数 n压测时间段内系统完成的请求总量,例如 1200 个请求。
    n=1200\displaystyle n = 1200
  2. 平均响应时间 L每个请求从发出到收到响应平均花了 0.5 秒。
    L=0.5\displaystyle L = 0.5
  3. 总时长 T统计这些请求所用的 60 秒。
    T=60\displaystyle T = 60
  4. 估算并发 C把三者代入后得到 10,表示这组吞吐与延迟背后大约对应 10 个并发用户。
    C=1200×0.560=10\displaystyle C = \frac{1200 \times 0.5}{60} = 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 演示

python52 行
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)
目的:用一个标准库实现的迷你压测器,演示 Locust 的核心思想——用一批并发执行体持续向服务端发请求,并计算 QPS 与平均延迟。
  1. ThreadPoolExecutor(max_workers=self.concurrency)同时安排 n 个“服务员”并行发请求,模拟并发用户。
  2. pool.submit(self._send, i)把每个请求任务投递给空闲线程,类似用户进店后立刻开台。
  3. futures = [...]收集这一批请求的 Future 对象,表示所有任务都已派发。
  4. for future in futures: latencies.append(future.result())等服务团队全部回单,再统计每笔耗时。
  5. qps = self.success_count / self.duration串联第 1 节的 QPS 公式,用成功数除以压测时长。
  6. 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 生成的可执行脚本通常以什么格式保存?

单选题 · easy

  • A.XML 文件保存组件树← 正确答案
  • B.JSON 文件保存采样
  • C.YAML 文件保存场景
  • D.Python 文件保存逻辑

答案:A。JMeter 将线程组、采样器、监听器等组件序列化为 .jmx 文件,本质是 XML。JSON、YAML、Python 分别是其他工具的脚本形式,不是 JMeter 的默认存储格式。

Q2. Locust 用哪种方式支持高并发用户?

单选题 · easy

  • A.每个用户一个进程
  • B.gevent 协程调度← 正确答案
  • C.浏览器多标签页
  • D.Java 线程池并发

答案:B。Locust 使用 gevent 协程模拟大量用户,而不是为每个用户分配操作系统线程。多进程不是 Locust 的默认单位,浏览器和 Java 线程池也与 Locust 无关。

Q3. k6 的测试场景由什么语言编写?

单选题 · mid

  • A.JavaScript ES6 脚本← 正确答案
  • B.Python 脚本和装饰器
  • C.Go 原生编译代码
  • D.XML 图形化组件

答案:A。k6 的脚本语言是 JavaScript,虽然它的运行引擎用 Go 实现,但用户不需要写 Go。Python 是 Locust 的语言,XML 是 JMeter 的格式。

Q4. 相比 JMeter 线程模型,Locust 协程模型最大特点是什么?

单选题 · mid

  • A.每个用户独占一个核心
  • B.用轻量协程降低等待← 正确答案
  • C.无法处理网络 I/O 请求
  • D.必须关闭 JVM 才能运行

答案:B。协程在等待 I/O 时主动让出控制权,能在一个线程上处理大量连接,因此 Locust 更省资源。它并不是独占 CPU 核心,也能很好地处理网络 I/O,更不需要关闭 JVM。

Q5. 实时观察压测的 QPS 与延迟,哪种做法最合理?

单选题 · mid

  • A.等待压测结束看聚合报表
  • B.压测期间不做任何监控
  • C.接入 Prometheus 和 Grafana← 正确答案
  • D.从日志 grep 错误信息

答案:C。实时监控能看出压测过程中曲线何时开始恶化,而只看最终报表会丢失拐点信息。k6 与 Grafana 生态集成可以实时呈现吞吐、延迟和错误率,方便定位瓶颈。

Q6. Little's Law 中 C = nL/T,其中 L 代表什么?

单选题 · mid

  • A.平均响应时间← 正确答案
  • B.每秒请求数量
  • C.单次任务请求数
  • D.压测运行总时长

答案:A。L 是平均响应时间,T 是统计时长,n 是完成请求数。C = nL/T 用来估算并发用户数。每秒请求数是 QPS,单次任务请求数与公式无关。

Q7. 理论 QPS 1000,500 并发只跑到 600,最合理的推断是什么?

单选题 · hard

  • A.系统可能已接近当前容量上限← 正确答案
  • B.应立刻切换另一个压测工具
  • C.500 并发一定还远远不够
  • D.调大并发后性能必然线性上升

答案:A。理论值通常是理想上限,实测 600 很可能说明瓶颈已经出现。换工具不能解决容量问题,线性上升也不成立,需要继续做资源分析和瓶颈定位。

Q8. 压测定位 AI 推理服务瓶颈时应采集哪些指标?

多选题 · hard

  • A.首字延迟与 P95/P99 分位← 正确答案
  • B.GPU 显存利用与排队情况← 正确答案
  • C.训练集大小与标注准确率
  • D.模型参数量与层数设计

答案:AB。容量规划关注的是服务在并发下的响应时间和资源排队情况,因此首字延迟、分位延迟、GPU 显存和排队长度是关键。训练集、标注准确率、参数量和层数是模型训练阶段关心的指标。

Q9. 相比 JMeter,k6 更受云原生团队欢迎的原因有哪些?

多选题 · mid

  • A.脚本使用 JavaScript 编写← 正确答案
  • B.轻量级且资源占用低← 正确答案
  • C.原生对接 Grafana 生态← 正确答案
  • D.自带 Windows 桌面 GUI

答案:ABC。k6 用 JS 写脚本、用 Go 并发实现轻量高并发,并且与 Grafana 生态结合紧密。它没有传统意义上的 Windows 桌面 GUI,因此 D 错误。

Q10. 压测选型需要综合考虑哪些因素?

多选题 · hard

  • A.团队熟练使用的语言← 正确答案
  • B.被测系统的网络协议← 正确答案
  • C.是否需要分布式压测← 正确答案
  • D.压测机所在机房楼层

答案:ABC。团队语言影响脚本维护成本,被测协议决定工具边界,分布式能力决定压测规模上限。机房楼层属于物理位置,不是压测工具选型的核心依据。