Artillery 性能测试:HTTP 与 WebSocket 负载
Artillery 性能测试实战:从 HTTP 到 WebSocket 全场景负载
在现代应用体系中,性能测试早已不是可选项,而是交付质量的关键防线。Artillery 作为一款开源的 Node.js 性能测试工具,凭借声明式配置、多协议支持和极低的上手门槛,迅速成为开发团队的主流选择。本教程将带你从零开始,掌握 Artillery 的核心概念,并分别完成 HTTP 接口负载测试 与 WebSocket 长连接压力测试。
为什么选择 Artillery?
市面上的压测工具很多,Artillery 的独特优势在于:
- 声明式 YAML 配置:无需编写脚本,一份配置文件即可描述所有测试场景。
- 原生支持 HTTP/HTTPS、WebSocket、Socket.io、Playwright:一套工具覆盖 API、实时通信、端到端浏览器测试。
- 内置性能指标:自动采集延迟百分位数(p50/p95/p99)、并发数、请求速率、错误率等关键数据。
- 扩展性强:通过插件机制可自定义引擎、报告器,并能与 CI/CD 管道无缝集成。
- 完全免费开源:无任何功能限制,适合个人开发者及企业团队。
接下来,我们将通过环境安装、HTTP 测试编写、WebSocket 测试实现三个环节,带你系统性掌握 Artillery 性能测试。
环境安装与快速验证
Artillery 基于 Node.js,请确保你的环境已安装 Node.js 18+。如需管理 Node.js 版本,推荐使用 nvm。
# 全局安装 Artillery
npm install -g artillery@latest
# 验证安装
artillery version
看到版本信息输出即表示安装成功。推荐使用最新版的 Artillery(v2+),它带来了性能优化和更清晰的命令结构。
第一节:HTTP 负载测试从入门到进阶
HTTP 测试是性能测试中最常见的场景。我们将从一个简单的 API 压测开始,逐步扩展到多阶段、数据驱动的复杂场景。
基础测试脚本:http-basic.yml
创建一个 http-basic.yml 文件,用极简的 YAML 定义你的第一个测试:
config:
target: "https://httpbin.org" # 目标基础地址
phases:
- duration: 30 # 测试持续30秒
arrivalRate: 10 # 每秒创建10个新虚拟用户
name: "热身阶段"
scenarios:
- name: "获取IP并发布数据"
flow:
- get:
url: "/ip"
- post:
url: "/post"
json:
username: "artillery_user"
timestamp: "{{ $timestamp }}"
脚本说明:
config.target:所有请求的公共前缀,避免在每一步中重复书写完整 URL。phases:定义负载注入的“阶段”,此处为单一阶段,持续 30 秒,每秒到达 10 个虚拟用户(VU)。这意味着大约总请求数为30×10 = 300次。scenarios:虚拟用户执行的具体操作。这里按顺序执行一个 GET 请求和一个 POST 请求,POST 请求体使用 Artillery 内置函数$timestamp生成动态时间戳。
执行测试并查看报告
使用 run 命令启动测试:
artillery run http-basic.yml
Artillery 会实时打印中间统计,并在测试结束时输出类似下面的汇总报告:
--------------------------------------
Summary report @ 10:15:23(+0800)
--------------------------------------
http.codes.200: .......................... 600
http.request_rate: ........................ 20/sec
http.requests: ............................ 600
http.response_time:
min: ..................................... 80
max: ..................................... 320
median: .................................. 150
p95: ..................................... 280
p99: ..................................... 300
vusers.created: ............................ 300
vusers.failed: .............................. 0
关键指标解读:
http.request_rate:每秒完成的请求数,接近arrivalRate × 场景请求数(本例中为 10×2=20)。- 响应时间百分位数:p95 小于 300ms 表示 95% 的请求在 300ms 以内完成,这是判断性能是否达标的黄金指标。
vusers.failed:失败虚拟用户数,应尽早关注并归零。
进阶 HTTP 配置:多阶段负载与动态数据
现实中的流量是变化的。Artillery 支持多阶段负载,可以模拟突增、平稳、衰减等复杂模式。此外还支持从 CSV 文件加载测试数据,实现参数化。
config:
target: "https://jsonplaceholder.typicode.com"
phases:
- duration: 60
arrivalRate: 5
name: "低负载预热"
- duration: 120
arrivalRate: 20
name: "持续高峰"
- duration: 60
arrivalRate: 5
name: "逐步退出"
payload:
path: "users.csv"
fields:
- "userId"
- "userName"
order: sequence # 按顺序循环取值
scenarios:
- name: "用户信息查询"
flow:
- get:
url: "/users/{{ userId }}"
capture:
- json: "$.name"
as: "apiUserName"
- think: 1 # 模拟用户思考时间1秒
- log: "API返回的用户名: {{ apiUserName }},CSV中的用户名: {{ userName }}"
新增要点:
phases数组:定义3个阶段,总计240秒。阶段间的切换是平滑的,Artillery 会自动调整虚拟用户创建速率。payload:从users.csv文件加载数据。CSV 第一行为列名,后续行为数据行。在请求中通过{{ userId }}引用。capture:从响应中提取数据。这里捕获 JSON 路径$.name的值并存入变量apiUserName,后续请求或日志均可使用。think:让虚拟用户在步骤间等待固定时间,更贴近真实用户行为。
执行该脚本前,请在同目录下创建 users.csv 文件:
userId,userName
1,Delphine
2,Ervin
3,Clementine
第二节:WebSocket 性能测试实战
对于聊天应用、实时协作工具、游戏服务器等场景,单纯的 HTTP 测试远远不够。Artillery 原生支持 WebSocket 协议,能够模拟长连接建立、消息发送与接收、连接关闭的全生命周期压力。
WebSocket 测试基础脚本
以下脚本连接到一个公开的 WebSocket 回显服务 wss://echo.websocket.org,发送消息并验证收到的回显。
config:
target: "wss://echo.websocket.org"
phases:
- duration: 30
arrivalRate: 5
ws:
# 可选的全局WebSocket配置,如设置子协议等
scenarios:
- name: "回显压力测试"
engine: "ws"
flow:
- connect: "/" # 建立WebSocket连接,路径为根路径
- send: "Hello Artillery {{ $randomString() }}"
- think: 0.5
- send: "第二条消息"
- think: 1
- close: 1000 # 正常关闭连接(状态码1000)
核心说明:
engine: "ws":必须为基于 WebSocket 的场景显式指定引擎,否则默认使用 HTTP 引擎。connect:第一个动作通常是connect,它发起 WebSocket 握手。后面可以接send、think、close等动作。- 连接复用:一个虚拟用户在一次场景迭代中只建立一个连接,可以连续发送多条消息,然后关闭。Artillery 会记录整个连接期间的消息延迟。
验证上行与下行消息
真实测试中,你往往需要关注“我发出消息后,多久能收到服务端的回应?”Artillery 的 ws 引擎支持消息匹配与响应时间捕获。
scenarios:
- name: "带响应验证的WS测试"
engine: "ws"
flow:
- connect: "/chat"
- send:
data: "{\"type\":\"login\",\"user\":\"tester\"}"
capture:
- json: "$.status"
as: "loginStatus"
match:
json: "$.status"
value: "ok"
- think: 0.5
- send:
data: "ping"
capture:
- regex: "pong"
as: "pongResponse"
match:
regex: "pong"
此脚本做了两件关键的事:
capture:与 HTTP 类似,从 WebSocket 接收的下一条消息中提取数据。这里既可以按 JSON 路径捕获,也可以用正则表达式捕获文本。match:断言收到的消息必须满足的条件。如果服务端返回的消息不符合预期,Artillery 会将此视为错误,并在报告中体现。
注意:
capture和match作用于发送send后接收到的第一个服务端消息。如果服务端会返回多条响应,你需要将逻辑拆分为多个send,每个send捕获对应的响应。
高级 WebSocket 场景:多用户顺序交互
在某些实时应用中,你需要模拟用户 A 发送消息后,用户 B 收到广播。Artillery 的 beforeScenario 与 afterScenario 钩子可以配合自定义处理器实现,但简单场景下也可以利用“顺序连接”来近似模拟。以下是两个虚拟用户模拟对话的简例:
scenarios:
- name: "用户对话模拟"
engine: "ws"
flow:
- connect: "/room/general"
- send: "{\"event\":\"join\",\"room\":\"general\"}"
- think: 1
- send: "Hello everyone!"
- think: 2
# 发送第二条带捕获的消息,验证服务端是否广播回自己
- send: "How are you?"
capture:
- json: "$.broadcastMessage"
as: "broadcast"
match:
json: "$.broadcastMessage"
value: "How are you?"
- close:
code: 1001
上述场景中,服务端需要将消息广播回发送者自身,才能匹配 match 断言。通过这种方式,你可以验证服务端在大量并发连接下的广播能力与延迟。
第三节:编写复杂场景与负载模型
场景权重与混合流量
当你的应用同时存在读和写操作,且读写比例不同时,可以通过 scenarios 数组 + weight 字段实现流量混合。
config:
target: "https://api.myservice.com"
phases:
- duration: 120
arrivalRate: 50
scenarios:
- name: "读操作 - 浏览商品"
weight: 7
flow:
- get:
url: "/products?page={{ $randomNumber(1,10) }}"
- name: "写操作 - 提交订单"
weight: 3
flow:
- post:
url: "/orders"
json:
productId: "{{ $randomNumber(1,1000) }}"
quantity: 1
此处每秒创建的 50 个虚拟用户中,平均有 70% 执行商品浏览(读操作),30% 执行订单提交(写操作)。weight 不必归一化,Artillery 会根据权重比例自动分配。
使用 before 与 after 钩子
若需要在场景开始前执行一次性操作(如登录获取 token),可以使用全局处理器。创建 processors.js:
// processors.js
module.exports = {
generateAuthToken: function (userContext, events, done) {
// 可在此处调用API获取token,然后写入userContext.vars
userContext.vars.token = 'bearer-secret-token-123';
return done();
}
};
在脚本中引用:
config:
target: "https://secure-api.example.com"
processor: "./processors.js"
phases:
- duration: 60
arrivalRate: 10
scenarios:
- name: "已认证请求"
flow:
- function: "generateAuthToken" # 每个虚拟用户都会执行一次
- get:
url: "/secure-data"
headers:
Authorization: "Bearer {{ token }}"
function 步骤允许你在虚拟用户的执行流中插入自定义逻辑,这对于需要动态令牌、签名计算的场景非常重要。
第四节:生成结构化报告与分析
命令行输出虽然即时,但不便于存档和分享。Artillery 内置了 HTML 报告生成器,只需在测试时启用记录。
artillery run --output report.json http-basic.yml
artillery report --output report.html report.json
执行完毕后,用浏览器打开 report.html 你会看到:
- 聚合统计:总请求数、平均响应时间、错误率等。
- 百分位分布图:直观展示响应时间的 p50、p75、p90、p95、p99。
- 并发数与请求速率的时间序列:便于定位性能拐点。
- 错误详情:列出所有失败及其原因。
你还可以通过插件将结果输出到 Datadog、Prometheus、CloudWatch 等监控系统,实现持续性能回归测试。
常见问题与排错
1. 连接超时或被拒
如果看到 ECONNREFUSED 或大量超时错误,请检查:
- 目标地址是否可从测试机访问(防火墙、VPN)。
- 是否达到了服务端的最大连接数限制。
- 请求路径是否正确(WebSocket 的
connect路径易写错)。
2. 虚拟用户始终为0
可能是 arrivalRate 太低或 duration 很短导致统计窗口未更新。增大速率并延长测试时间观察。
3. WebSocket 消息匹配失败
确保 capture 中的 JSON 路径或正则表达式与收到的实际消息格式完全一致。建议先用 - log: "{{}}" 打印收到的原始消息进行调试。
4. 内存不足
极高并发(如数千 VU)可能导致 Node.js 内存溢出。可以:
- 增加 Node.js 堆内存:
NODE_OPTIONS="--max-old-space-size=4096" artillery run ...。 - 使用
artillery run --scenario-name只运行特定场景。 - 考虑分布式执行(Artillery Pro 特性,但开源版可用多个实例手工分摊)。
总结与下一步
至此,你已经能够独立完成基于 Artillery 的 HTTP 和 WebSocket 性能测试。回顾一下关键知识点:
- 通过 YAML 配置 定义目标、负载阶段和虚拟用户行为。
- 利用
phases设计阶梯型、锯齿型或持续型压力曲线。 - 在场景中灵活使用 动态变量、数据文件、响应捕获与断言 来模拟真实业务。
- 通过 HTML 报告分析延迟分布,定位性能瓶颈。
Artillery 的官方文档(docs.artillery.io)包含了插件开发、PaaS 集成、Socket.io 引擎等高级主题。性能测试是一项持续实践,建议将 Artillery 脚本纳入 CI 流水线,每次代码变更后自动执行,守护应用性能基线。
现在,打开终端,编写你的第一份压测脚本,开始测量真实世界的性能吧!