使用 net/http 与 goroutine 模拟并发请求时,必须为 http.Client 显式设置 Timeout(如 5 秒),避免默认无超时导致高并发 hang 住;每个 goroutine 独立发起请求,用 sync.WaitGroup 等待完成,结果通过 chan Result 收集,压测后统计算 QPS、P95(排序取索引 n*95/100)、失败率(按 Err 非 nil 统计),并限制并发数防压垮本地环境。

用 net/http + goroutine 模拟并发请求
Go 原生 net/http 完全够用,不需要引入第三方 HTTP 客户端。重点是别让单个请求阻塞整个 goroutine —— 必须显式设置超时,否则压测会卡死或结果失真。
常见错误现象:http.DefaultClient 默认无超时,高并发下大量请求 hang 住;time.Sleep 在循环里硬等,导致并发数不可控。
- 创建带超时的 client:
http.Client{Timeout: 5 * time.Second} - 每个 goroutine 独立发起请求,避免共享连接池争抢(除非你明确要测连接复用)
- 用
sync.WaitGroup等待全部完成,别用time.Sleep估算执行时间 - 记录每个请求的耗时、状态码、是否出错,便于后续统计
如何统计 QPS、P95、失败率等关键指标
压测脚本的价值不在“跑起来”,而在“看得清”。不建议边压边打印日志——IO 会拖慢吞吐,也难聚合。应该把原始数据先存进 slice 或 channel,压完再统一分析。
容易踩的坑:用 float64 累加耗时再除以请求数算平均值,但 P95/P99 必须排序取分位点;并发写 map 不加锁直接 panic;没过滤 4xx/5xx 就计入成功率。
立即学习“go语言免费学习笔记(深入)”;
- 定义结构体存单次结果:
type Result struct { Latency time.Duration; Status int; Err error } - 所有结果写入
chan Result,主 goroutine 收集并关闭 channel - 排序后取
results[n*95/100]得 P95,注意切片索引越界 - 失败率 =
len(filterByErr(results)) / total,不是看 status 是否为 200
避免压垮本地开发环境的三个硬限制
很多人一上来就 for i := 0; i 起 goroutine,结果本机 CPU 100%、端口耗尽、DNS 查询超时,测的不是服务而是自己机器的崩溃阈值。
真实压力测试必须可控:控制并发数、总请求数、请求间隔。尤其注意 http.Transport 的默认连接限制,它会成为隐形瓶颈。
- 显式配置
Transport.MaxIdleConns和MaxIdleConnsPerHost,例如设为 200 - 用
rate.Limiter控制 QPS 上限,比如rate.NewLimiter(100, 1)表示每秒最多 100 个请求 - 总请求数建议通过 flag 参数传入,不要硬编码;并发数同理,初始从 10/50/100 逐步加压
- 务必在
main()开头加runtime.GOMAXPROCS(runtime.NumCPU()),否则默认只用一个 OS 线程
和 Dify 测试框架的集成要点
如果你已在用 Dify 内部测试框架,它的 YAML 配置和断言能力可以直接复用,但压力测试部分需自行补全 —— 它的 压力测试 类型只支持多协程模式,不提供分布式压测能力。
关键差异点:Dify 框架默认按「任务队列」调度回归测试,但压力测试要求低延迟、高吞吐,必须绕过队列直连目标服务。同时,它的 assert.Equal 只校验响应体,不处理性能指标。
- 复用其 YAML 解析逻辑加载 endpoint、headers、body 模板,但请求发送改用自定义 client
- 把 Dify 的
trace ID注入到每个请求 header(如X-Trace-ID),方便后端日志关联 - 压测报告不走它的 HTML 生成器,而是输出 JSON 到文件,供 Grafana 或内部看板消费
- 注意 Dify 断言库的
assert包非线程安全,性能统计阶段别混用
压测脚本最易被忽略的是资源清理和信号捕获——比如 Ctrl+C 中断后,goroutine 还在发请求、文件句柄没关、临时指标没 flush。这些细节不处理,多次运行后数据就不可信了。


















