如何压测API Testing
Performance Testing with Apache Bench | by Pankaj Gupta | Medium
Top 9 Methods for API Testing in Java | Comprehensive Guide
ab - Apache HTTP server benchmarking tool - Apache HTTP Server Version 2.4
性能测试工具 wrk 使用教程 - 犬小哈 - 博客园
wrk 是一款现代、轻量级且极高性能的 HTTP 基准测试工具(Benchmark Tool)。它最显著的特点是利用了异步事件驱动架构(如 Linux 的 epoll 或 macOS 的 kqueue),能用极少的线程在单机上产生巨大的并发压力。
简单来说,它是后端开发者用来压测接口 QPS (每秒查询率) 和 Latency (延迟) 的神器。
为什么用 wrk 而不是 Apache Bench (ab) 或 JMeter?
- 性能更强:
wrk 使用非阻塞 I/O,能比 ab 产生更高的并发,更能榨干服务器性能。 - 支持 Lua 脚本:这是
wrk 的杀手锏。你可以编写 Lua 脚本来构造复杂的请求(如生成动态 Token、随机参数、POST 复杂 JSON 等),而 ab 只能发静态请求。 - 轻量:没有 JMeter 那么重的 GUI,纯命令行,适合服务器端直接跑。
常用命令示例
最简单的压测命令:
1
| wrk -t12 -c400 -d30s http://localhost:8080/api/v1/user
|
-t12: 使用 12 个线程(一般设置为 CPU 核心数)。-c400: 保持 400 个 HTTP 连接(并发数)。-d30s: 持续压测 30 秒。--latency: (可选) 输出详细的延迟分布报告(P50, P90, P99)。
配合 Lua 脚本(进阶)
比如你要压测一个POST 接口,且需要JSON Body,可以写一个 post.lua:
1
2
3
4
| -- post.lua
wrk.method = "POST"
wrk.body = '{"billCheckId": 123, "type": "NC"}'
wrk.headers["Content-Type"] = "application/json"
|
wrk 的强大之处就在于它嵌入了 LuaJIT 解释器。这意味着你可以完全控制 HTTP 请求的生成逻辑,包括随机化参数、动态生成 Body、随机 Header、甚至根据响应调整请求。
以下是几个实用的 Lua 脚本模板,专门解决“随机分布请求”的问题。
1. 基础随机:随机请求不同的 URL 路径
比如你想压测 /user/1001 到 /user/9999 这样随机的用户详情页,防止服务器缓存热点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| -- 在压测开始前初始化随机种子
-- 注意:每个线程都有独立的 Lua 虚拟机,所以每个线程都需要初始化一次
math.randomseed(os.time())
request = function()
-- 生成 1 到 10000 之间的随机用户ID
local user_id = math.random(1, 10000)
-- 拼接路径
local path = "/user/" .. user_id
-- 返回定制的请求
-- wrk.format(method, path, headers, body)
return wrk.format("GET", path)
end
|
2. 权重随机:按比例访问不同接口
真实场景通常是混合的:80% 读请求,20% 写请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| math.randomseed(os.time())
local paths = {
"/products/list", -- 读列表
"/products/detail", -- 读详情
"/cart/add" -- 写操作(加购)
}
request = function()
local r = math.random(1, 100)
if r <= 60 then
-- 60% 概率访问列表
return wrk.format("GET", paths[1])
elseif r <= 90 then
-- 30% 概率访问详情 (60-90)
local pid = math.random(100, 500)
return wrk.format("GET", paths[2] .. "/" .. pid)
else
-- 10% 概率添加购物车 (90-100)
local body = '{"productId": ' .. math.random(100, 500) .. '}'
wrk.headers["Content-Type"] = "application/json"
return wrk.format("POST", paths[3], nil, body)
end
end
|
3. POST 请求:随机 Body 内容
压测“创建订单”或“短链接生成”接口时,每次 Body 必须不同,否则会被数据库唯一索引拦住。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| math.randomseed(os.time())
-- 辅助函数:生成随机字符串
function random_str(length)
local chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
local res = ""
for i = 1, length do
local rand = math.random(#chars)
res = res .. string.sub(chars, rand, rand)
end
return res
end
request = function()
-- 动态生成 Body JSON
local random_url = "http://example.com/" .. random_str(8)
local body = '{"originalUrl": "' .. random_url .. '"}'
-- 设置 Header
wrk.headers["Content-Type"] = "application/json"
-- 发送 POST
return wrk.format("POST", "/create", nil, body)
end
|
4. 高级技巧:每个线程独立的“用户身份” (Token)
如果你要压测需要登录的接口,可以给每个线程分配不同的 Token。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| -- 每个线程启动时调用一次 setup
-- thread: 代表当前线程对象
function setup(thread)
-- 给每个线程分配一个唯一的 ID (1, 2, 3...)
thread:set("id", _G.thread_id_counter)
_G.thread_id_counter = (_G.thread_id_counter or 0) + 1
end
-- 线程初始化时运行
function init(args)
-- 根据线程 ID 生成伪造 Token
-- 比如线程1用 user1_token,线程2用 user2_token
local token = "Bearer user_" .. id .. "_token_secret"
-- 设置全局默认 Header,该线程后续所有请求都会带上这个
wrk.headers["Authorization"] = token
end
request = function()
return wrk.format("GET", "/profile")
end
|
总结
- 随机 URL:在
request() 函数里拼装 path。 - 随机 Body:在
request() 里拼装 JSON 字符串。 - 按比例混合:用
math.random(1, 100) 做 if-else 分流。 - 运行方式:一定要带上
--script 参数。
如何查看CPU核心数
lscpu

可以看出一共有16个核心,每个核心可以当两个线程使用(Intel 的超线程技术)
1. 为什么“1个核”能变“2个核”?
- 物理层面:你的 CPU 确实只有 16 个完整的物理核心(Physical Cores),也就是有 16 套独立的 ALU(运算单元)和缓存。
- 逻辑层面:但是,物理核心内部的执行资源(如寄存器、指令队列)往往有富余。SMT 技术允许操作系统把 1 个物理核心 识别为 2 个逻辑核心。