3、测试工具链:wrk2、h2load、locust、自定义C++测试客户端、Python asyncio压测脚本
聊到性能测试,很多人的第一反应是「找个工具,跑一下」。嗯,这话没错,但跑出来的数字到底能不能信,就看你选什么工具、怎么用了。我见过不少团队,拿个通用压测工具随便压一压,然后对着 QPS 数字拍脑袋做容量规划,结果上线第一天就崩了。
说白了,量化交易系统的压测,跟 Web 压测完全是两码事。我们的核心链路是订单处理、行情推送、风控校验,延迟敏感度是微秒级的。你拿个测 HTTP 的工具去压 TCP 直连的行情网关,那数据基本是废的。
今天我就把这几年用过的、踩过坑的几款工具,挨个给你捋一遍。哪些场景该用谁,怎么配参数才能拿到真实数据,我都会说清楚。
3.1 wrk2:HTTP 压测的「老黄牛」
wrk2 是 wrk 的增强版,最大的改进是支持稳定的请求到达率。什么意思?就是它能保证每秒发出去的请求数量是恒定的,而不是「尽力而为」地猛发。这一点对延迟测试特别重要。
适用场景:REST API 接口、WebSocket 网关的 HTTP 升级握手。
我常用的命令模板:
wrk2 -t4 -c100 -d30s -R2000 --latency http://192.168.1.100:8080/api/order
-t4:4 个线程-c100:100 个并发连接-d30s:持续 30 秒-R2000:每秒发 2000 个请求--latency:输出延迟分布
-R 参数别设太高,先按系统预估 QPS 的 80% 起步。我曾经一上来就设 5000,结果服务端直接拒绝连接,我还以为是工具坏了。
3.2 h2load:HTTP/2 和多路复用的利器
如果你的交易网关支持 HTTP/2(现在很多新一代系统都支持了),那 h2load 是首选。它原生支持多路复用,能模拟多个 Stream 在一条连接上并发请求。
为什么重要? 因为 HTTP/2 的多路复用特性,能大幅减少连接建立的开销。你用 wrk2 压 HTTP/1.1 得出的延迟,跟 h2load 压 HTTP/2 的结果,可能差一个数量级。
h2load -n100000 -c100 -m10 https://192.168.1.100:443/api/order
-n100000:总共发 10 万请求-c100:100 个并发连接-m10:每个连接最多 10 个并发 Stream
--no-tls 参数。我有个同事因为这个参数没加,折腾了一下午。
3.3 locust:Python 生态的「瑞士军刀」
Locust 的优势在于灵活。你可以用 Python 写任意复杂的用户行为脚本,比如先登录、再查询、再下单、再撤单。这种场景化的压测,wrk2 和 h2load 都做不到。
我一般用它来压测什么?
- 模拟多个交易员同时操作
- 测试风控规则的并发校验
- 验证数据库连接池在压力下的表现
一个简单的 locustfile.py 示例:
from locust import HttpUser, task, between
class TraderUser(HttpUser):
wait_time = between(0.1, 0.5)
@task
def place_order(self):
self.client.post("/api/order", json={
"symbol": "BTCUSDT",
"side": "buy",
"price": 50000,
"quantity": 0.1
})
3.4 自定义 C++ 测试客户端:微秒级延迟的终极方案
说实话,前面几个工具在大多数场景下够用了。但如果你要压测的是低延迟行情网关、FPGA 加速卡、或者内存撮合引擎,那它们的精度就不够了。为什么?因为 wrk2 和 Locust 的计时精度在毫秒级,而我们的系统延迟是微秒级的。
这时候,就得自己写 C++ 客户端了。
我一般用 libuv 或 boost.asio 做事件循环,配合 clock_gettime 获取纳秒级时间戳。核心逻辑很简单:
#include <uv.h>
#include <chrono>
void on_write(uv_write_t* req, int status) {
auto end = std::chrono::high_resolution_clock::now();
auto start = *(std::chrono::high_resolution_clock::time_point*)req->data;
auto latency = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
// 记录延迟
}
int main() {
uv_loop_t *loop = uv_default_loop();
uv_tcp_t *socket = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
uv_tcp_init(loop, socket);
// ... 连接、发送、接收逻辑
uv_run(loop, UV_RUN_DEFAULT);
return 0;
}
high_resolution_clock,并且要在发送前和收到响应后各取一次时间戳。中间不能有任何阻塞操作,否则测出来的延迟是假的。
3.5 Python asyncio 压测脚本:快速原型验证
有时候你只是想快速验证一个想法,比如「这个接口在 1000 并发下会不会超时」。这时候写 C++ 客户端就太重了。Python asyncio 配合 aiohttp 或 websockets,几分钟就能写一个可用的压测脚本。
import asyncio
import aiohttp
import time
async def send_order(session, sem):
async with sem:
start = time.perf_counter()
async with session.post("http://192.168.1.100:8080/api/order",
json={"symbol": "BTCUSDT", "price": 50000}) as resp:
end = time.perf_counter()
latency = (end - start) * 1000 # 毫秒
return latency
async def main():
sem = asyncio.Semaphore(100) # 控制并发数
async with aiohttp.ClientSession() as session:
tasks = [send_order(session, sem) for _ in range(10000)]
latencies = await asyncio.gather(*tasks)
print(f"平均延迟: {sum(latencies)/len(latencies):.2f}ms")
asyncio.run(main())
3.6 工具选型对比
| 工具 | 适用场景 | 延迟精度 | 学习成本 | 我的推荐指数 |
|---|---|---|---|---|
| wrk2 | HTTP/1.1 REST API | 毫秒级 | 低 | ⭐⭐⭐⭐ |
| h2load | HTTP/2 多路复用 | 毫秒级 | 中 | ⭐⭐⭐⭐ |
| locust | 复杂业务场景 | 毫秒级 | 中 | ⭐⭐⭐ |
| C++ 客户端 | 低延迟核心链路 | 微秒级 | 高 | ⭐⭐⭐⭐⭐ |
| Python asyncio | 快速原型验证 | 毫秒级 | 低 | ⭐⭐⭐ |
3.7 工具链选择的核心逻辑
说了这么多,你可能会问:「那我到底该用哪个?」
我的建议是:不要只依赖一个工具。一个成熟的量化交易团队,应该同时维护 2-3 套压测工具链。
- 日常回归测试:用 wrk2 或 h2load,跑得快,结果稳定
- 复杂场景模拟:用 locust,写脚本灵活
- 核心链路压测:必须上自定义 C++ 客户端,精度才有保障
- 快速验证:Python asyncio 脚本,改起来快
下面这张图,是我自己总结的工具链选择流程,你可以参考一下:
嗯,工具链这块就先聊到这儿。记住一句话:没有最好的工具,只有最合适的场景。下一节我们会深入聊聊如何设计压测场景和参数,让这些工具真正发挥价值。