4、压测工具选型:JMeter、wrk、kafka-producer-perf-test、自定义Go压测脚本的优缺点对比
做延迟压测,选对工具等于成功了一半。
我见过不少团队,一上来就拍脑袋选JMeter,结果压到一半发现线程数上不去,延迟数据还飘得厉害。也见过有人用wrk压HTTP接口,压得挺爽,但换到Kafka就傻眼了——wrk根本不支持。
所以这一章,咱们把市面上主流的几个压测工具拉出来遛遛。JMeter、wrk、kafka-producer-perf-test,还有我自己经常写的自定义Go脚本。每个工具我都会结合项目里的真实踩坑经历来讲,帮你少走弯路。
核心观点:没有完美的工具,只有最适合场景的选择。延迟压测尤其敏感,工具本身的调度开销、线程模型、统计精度,都会直接影响你的测试结论。
4.1 JMeter:功能全面,但小心它的“重量级”
JMeter是老牌压测工具了。图形化界面、插件生态丰富、支持各种协议——HTTP、TCP、JMS、Kafka(通过插件)。看起来什么都能干。
但说实话,我在做交易系统延迟压测时,很少用它。为什么?
- 线程模型太重:JMeter每个虚拟用户就是一个Java线程。压到几百并发时,GC暂停就会引入额外的延迟抖动。你测的是系统延迟,还是JVM的GC延迟?
- 统计精度不够:JMeter默认的聚合报告是秒级采样。对于微秒级的延迟波动,基本抓不住。我曾经用JMeter压一个内存队列,结果报告显示平均延迟0.1ms,但实际用Go脚本抓到的P99是0.8ms——差了8倍。
- 分布式压测配置繁琐:想压到万级QPS?你得搭Master-Slave集群,网络同步、时间校准、结果聚合,每一步都是坑。
我的建议:JMeter适合做功能验证和低并发场景的基准测试。比如你刚搭好一个数据总线,想看看接口通不通、基本延迟在不在合理范围,用JMeter快速跑一轮没问题。但别用它做高精度延迟分析。
4.2 wrk:轻量级HTTP利器,但协议受限
wrk是我个人非常喜欢的工具。它基于Reactor模式,用C语言编写,单机就能压出几十万QPS。启动快、结果清晰、延迟统计到毫秒级。
它的优点很明显:
- 性能极高:相比JMeter,wrk的线程模型非常轻量。它用少量线程配合epoll/kqueue处理海量连接,几乎不产生额外开销。
- Lua脚本扩展:你可以写简单的Lua脚本来自定义请求内容、参数化、甚至做简单的响应校验。
- 延迟分布一目了然:wrk默认输出延迟的分布情况,包括P50、P75、P90、P99。对于快速评估系统延迟特性非常方便。
但wrk的短板也很致命:
- 只支持HTTP:你没法用它压Kafka、RabbitMQ、TCP私有协议。交易数据总线很多场景是二进制协议或消息队列,wrk完全无能为力。
- 单机模式:wrk没有分布式能力。单机压到瓶颈后,你没法简单扩展。
- 统计粒度粗:wrk的延迟统计是基于整个测试周期的。你没法看到时间序列上的延迟变化——比如某段时间突然抖动,wrk只会把它平均掉。
注意:我曾经用wrk压一个HTTP网关,结果发现P99延迟从2ms跳到了50ms。wrk报告里只显示一个平均值,我根本不知道抖动发生在哪一秒。后来换了Go脚本按秒输出延迟分布,才定位到是网关的定时任务触发了Full GC。
4.3 kafka-producer-perf-test:专为Kafka而生
这个工具是Kafka官方自带的压测脚本,位于$KAFKA_HOME/bin/目录下。它的定位非常明确:只压Kafka Producer的吞吐和延迟。
用法很简单:
bin/kafka-producer-perf-test.sh \
--topic test-topic \
--num-records 1000000 \
--record-size 1024 \
--throughput 10000 \
--producer-props bootstrap.servers=localhost:9092
它的优势:
- 原生支持Kafka:直接调用Kafka Producer API,不需要额外适配。压出来的结果就是生产环境的真实表现。
- 延迟统计详细:它会输出发送延迟的分布,包括50th、75th、95th、99th百分位。而且支持按时间窗口输出,方便观察延迟波动。
- 吞吐控制精准:你可以通过
--throughput参数限制发送速率,模拟不同的负载场景。
但它的缺点也很明显:
- 只适用于Kafka:你没法用它压别的中间件。
- 单机模式:和wrk一样,没有分布式能力。想压集群极限?你得自己写脚本在多台机器上同时跑。
- 定制能力弱:你没法在发送前后插入自定义逻辑——比如记录发送时间戳、模拟业务字段、做响应校验。
我的经验:kafka-producer-perf-test适合做Kafka集群的基准测试。我每次搭建新的Kafka集群,第一件事就是用这个工具跑一轮延迟基线。但如果你要压的是“数据总线”这种包含多个环节的链路,它就不够用了。
4.4 自定义Go压测脚本:灵活、精准、可控
说到这儿,你可能会问:那到底用什么工具做交易数据总线的延迟压测?
我的答案是:自定义Go脚本。
为什么是Go?
- 协程轻量:Go的goroutine比Java线程轻量得多。你可以轻松启动几万个并发协程,而不会引入明显的调度开销。
- 延迟统计精准:你可以用
time.Now()或time.Since()精确到纳秒级。配合直方图库(比如github.com/HdrHistogram/hdrhistogram-go),能输出P50、P99、P999甚至P9999的延迟。 - 完全可控:你想怎么压就怎么压。可以按时间窗口输出延迟分布,可以在请求前后插入日志,可以模拟业务逻辑,可以动态调整并发数。
- 跨平台部署:编译成二进制文件,扔到服务器上就能跑。不需要装JRE、不需要配环境。
下面是我常用的一个压测脚本骨架:
package main
import (
"fmt"
"time"
"sync"
"github.com/HdrHistogram/hdrhistogram-go"
)
func main() {
// 创建一个直方图,记录1微秒到1分钟的延迟
hist := hdrhistogram.New(1, 60_000_000, 3)
var wg sync.WaitGroup
concurrency := 1000
totalRequests := 100000
start := time.Now()
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < totalRequests/concurrency; j++ {
t1 := time.Now()
// 这里放你的业务请求
// e.g., sendKafkaMessage()
elapsed := time.Since(t1)
hist.RecordValue(elapsed.Microseconds())
}
}()
}
wg.Wait()
elapsed := time.Since(start)
fmt.Printf("总耗时: %v\n", elapsed)
fmt.Printf("P50: %v\n", hist.ValueAtPercentile(50))
fmt.Printf("P99: %v\n", hist.ValueAtPercentile(99))
fmt.Printf("P999: %v\n", hist.ValueAtPercentile(99.9))
}
当然,自定义脚本也有代价:
- 开发成本:你得自己写代码、处理并发、做统计。不像JMeter那样开箱即用。
- 需要维护:压测脚本也是代码,需要版本管理、测试、文档。
- 协议适配:如果数据总线用的是私有协议,你得自己实现客户端。
我的建议:对于交易数据总线这种对延迟极度敏感的系统,花两天时间写一个Go压测脚本,远比花两天时间调JMeter参数要值得。你想想看,压测工具本身的性能开销如果占了总延迟的10%,那你的测试结论还有什么意义?
4.5 工具对比总表
为了方便你快速决策,我把四个工具的核心维度整理成了表格:
| 维度 | JMeter | wrk | kafka-producer-perf-test | 自定义Go脚本 |
|---|---|---|---|---|
| 协议支持 | HTTP、TCP、JMS、Kafka等 | 仅HTTP | 仅Kafka Producer | 任意(需自行实现) |
| 并发模型 | Java线程(重量级) | Reactor(轻量级) | Java线程(中等) | Go协程(极轻量) |
| 延迟统计精度 | 秒级(较粗) | 毫秒级(中等) | 毫秒级(中等) | 纳秒级(精准) |
| 分布式能力 | 支持(配置复杂) | 不支持 | 不支持 | 需自行实现 |
| 学习成本 | 低(图形化) | 低(命令行) | 低(命令行) | 高(需编程) |
| 适用场景 | 功能验证、低并发基准 | HTTP接口快速压测 | Kafka集群基准测试 | 高精度延迟压测、复杂链路 |
4.6 我的选型建议
说了这么多,到底怎么选?我总结三条原则:
- 快速验证用wrk:如果你只是压HTTP接口,想看看基本延迟水平,wrk是最快的选择。一条命令搞定。
- Kafka基准用官方工具:kafka-producer-perf-test是Kafka团队自己写的,压出来的结果最贴近生产。别自己造轮子。
- 高精度延迟压测用Go脚本:交易数据总线的延迟要求通常是微秒级到毫秒级。这时候工具本身的性能开销必须足够低。Go脚本是唯一能让你精确控制每个环节的选择。
避坑指南:我曾经用JMeter压一个延迟要求<1ms的数据总线,结果JMeter自己就占了0.3ms的调度开销。测出来的延迟是0.8ms,实际系统延迟只有0.5ms。这个误差直接导致我们误判了系统性能,多花了两周做优化。后来换成Go脚本,才看到真实数据。
嗯,工具选型就聊到这儿。下一章咱们会深入讲压测环境的搭建和校准——你会发现,环境本身对延迟的影响,可能比工具还大。