不能。go test -bench 专为单函数基准测试设计,测量稳定性能指标(如 ns/op),不支持并发控制、QPS调节、错误率统计、p95延迟计算或持续压测,其 b.N 为串行迭代次数,b.RunParallel 亦无失败分类与超时管理能力。

go test -bench 能不能直接当压测脚本用
不能。Go 自带的 go test -bench 是为基准测试(benchmark)设计的,目标是测量单个函数在受控、无干扰环境下的**稳定性能指标**(如 ns/op),不是模拟并发请求、维持负载或观察系统在压力下的行为。它默认只跑一次预热 + 若干轮次取平均,不支持持续运行、动态调参、结果聚合(如 p95、错误率)、或对接监控端点。
用 testing.B 编写 bench 函数时常见的误用
很多人把 BenchmarkXxx 当成压测入口,但容易忽略 *testing.B 的隐含约束:
-
b.N是框架自动调整的迭代次数,不是并发数 —— 它只控制循环执行次数,所有迭代默认串行(除非你手动启 goroutine) -
b.RunParallel确实能启多 goroutine,但它只为「消除单次调用开销」而设,不提供请求计数、超时控制、失败重试、或结果分类(成功/失败/超时) - 没有内置的 QPS 控制、阶梯加压、或采样日志能力;一旦
b.N跑完就退出,无法持续压测 5 分钟并每秒输出当前 TPS
示例:下面这段看似“并发”,实际只是让 10 个 goroutine 同时跑 b.N / 10 次,且无法知道哪次失败了:
func BenchmarkHTTPGet(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
http.Get("http://localhost:8080/health")
}
})
}真正适合压测的替代方案:自己写 main 包 + time/ticker + sync/atomic
如果不需要复杂压测平台(如 k6、vegeta),又想用 Go 快速写一个可复现、可调试的压力脚本,推荐绕过 go test,直接写 main 函数,用标准库组合实现核心能力:
- 用
time.Ticker或time.AfterFunc控制发压节奏(固定 QPS 或突发流量) - 用
sync.WaitGroup+sync/atomic统计成功/失败/超时请求数、总耗时、最大延迟 - 用
http.Client配置Timeout和Transport(如连接池大小),避免默认设置拖慢结果 - 把关键指标(如每秒请求数、p95 延迟)按秒打印到 stdout,方便用
tsv工具后续分析
最小可行示例(固定并发数 + 计数):
func main() {
var (
success, fail, timeout int64
wg sync.WaitGroup
client = &http.Client{Timeout: 2 * time.Second}
)
concurrency := 50
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for range time.Tick(100 * time.Millisecond) { // 每 100ms 发 1 请求 → 目标 QPS=10
start := time.Now()
_, err := client.Get("http://localhost:8080/api")
dur := time.Since(start)
if err != nil {
atomic.AddInt64(&fail, 1)
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
atomic.AddInt64(&timeout, 1)
}
} else {
atomic.AddInt64(&success, 1)
}
}
}()
}
time.Sleep(30 * time.Second)
fmt.Printf("success=%d fail=%d timeout=%d\n", atomic.LoadInt64(&success), atomic.LoadInt64(&fail), atomic.LoadInt64(&timeout))
}为什么别硬套 testing.B 做长时间压测
除了语义不符,还有几个实际坑点:
-
go test -bench会主动限制最大内存和 CPU 时间,遇到卡顿可能提前终止,且不会告诉你原因 - 所有
Benchmark*函数必须放在_test.go文件里,而压测脚本往往需要读配置文件、连数据库、打日志 —— 这些在测试文件中做会污染测试边界 - 无法捕获 SIGINT(Ctrl+C)优雅停止;
testing.B生命周期由框架完全控制,你不能插入手动中断逻辑 - go 1.21+ 对
-benchmem等参数做了更激进的 GC 干预,可能让压测结果失真(比如高分配场景下看到的延迟其实是 GC STW)
真正需要长期运行、可观测、可调参的压测,就老老实实写 main.go。testing.B 只适合「这个函数快不快」,不适合「服务扛不扛得住」。


















