Hey 源码结构全景图:HTTP 压测工具 hey.go 与 requester 包的完整架构导览
【免费下载链接】heyHTTP load generator, ApacheBench (ab) replacement项目地址: https://gitcode.com/GitHub_Trending/he/hey
hey 是一款用 Go 语言编写的轻量级 HTTP 负载生成器(load generator),常被视为 ApacheBench(ab)的现代替代品。整个项目只有两个入口文件加一个 requester/ 包,代码量极小,却是学习"并发压测工具如何设计"的绝佳样本。本文带你从 hey.go 走到 requester/requester.go,看清每一个模块的分工。
一、仓库文件地图:先看全景再拆细节
整个仓库非常扁平,核心就 8 个文件:
| 文件 | 职责 |
|---|---|
| hey.go | 程序入口:命令行参数解析、请求构造、启动压测 |
| hey_test.go | 入口层的单元测试 |
| requester/requester.go | 压测引擎核心:Work结构体与 worker 并发调度 |
| requester/report.go | 结果聚合:延迟统计、百分位、直方图 |
| requester/print.go | 输出渲染:Summary / CSV 两种模板 |
| requester/now_other.go | 非 Windows 平台的高精度计时 |
| requester/now_windows.go | Windows 平台计时(QueryPerformanceCounter) |
| requester/requester_test.go | requester 包的测试 |
周边工程文件还有 go.mod(仅依赖golang.org/x/net一个模块)、Makefile(跨平台构建 release 产物)和 Dockerfile。这种"入口 + 单包"的结构,让 hey 可以编译成零依赖的单个二进制文件。
二、入口层:hey.go 如何把命令行变成压测任务
hey.go 约 290 行,承担了全部"胶水"工作,逻辑可以拆成四步 🧩:
1. 命令行参数定义
文件开头用 Go 标准库flag声明了所有参数(hey.go#L41-L67):
-n请求总数(默认 200)、-c并发 worker 数(默认 50)-q每 worker 的 QPS 限流、-z压测持续时长-m/-H/-d/-D控制方法、请求头、请求体-h2开启 HTTP/2,-cpus控制 CPU 核数
2. 参数校验与请求构造
main()函数(hey.go#L106-L253)先做合法性校验(如-n不能小于-c),随后把参数翻译成标准库的http.Request:正则解析自定义头、Basic Auth,拼装 User-Agent(自动带上hey/0.0.1标识)。
3. 装配 Work 并启动
关键在 hey.go#L224-L252:把所有配置塞进requester.Work结构体,调用w.Init()和w.Run()。同时它注册了两个"停止信号"——
- Ctrl+C:通过
os/signal捕获中断信号,触发w.Stop() -z定时停止:后台 goroutine 睡满指定时长后自动调用w.Stop()
也就是说,hey.go 自己不执行任何 HTTP 请求,它只负责"配置 + 启停控制",真正的引擎在 requester 包里。
三、引擎层:requester 包的并发模型
requester 包是 hey 的心脏,四个文件各司其职。
Work 结构体:一次压测的全部状态
requester/requester.go 中的Work(requester.go#L49-L100)持有压测配置(N/C/QPS/Timeout/H2 等),以及两个关键通道:
results chan *result:结果管道,缓冲上限 100 万条stopCh chan struct{}:优雅停机信号
生命周期只有四个方法:Init()→Run()→(外部可触发)Stop()→Finish()。
runWorkers:C 个并发 worker 怎么跑
requester.go#L235-L264 的runWorkers()展示了 hey 最核心的并发设计:
- 构建一个共享的
http.Transport——MaxIdleConnsPerHost取并发数 C(上限 500),让 TCP 连接在 worker 间复用; - 若开启
-h2,用http2.ConfigureTransport升级传输层; - 启动 C 个 goroutine,每个执行
runWorker(client, N/C),用sync.WaitGroup等待全部完成。
每个 worker 内部(requester.go#L208-L233)用time.Ticker实现 QPS 限流,并在每轮循环开头检查stopCh,保证收到停止信号后能立刻退出。
makeRequest:一次请求的"分段计时"
requester.go#L146-L206 是理解 hey 统计精度的关键。它通过httptrace.ClientTrace钩住请求生命周期的 6 个时间点,把一次请求拆成独立耗时:
| 阶段 | 含义 |
|---|---|
| DNS+dialup | 建立 TCP 连接总耗时 |
| DNS | 纯 DNS 解析耗时 |
| req write | 写出请求耗时 |
| resp wait | 首字节延迟(TTFB) |
| resp read | 读取完整响应耗时 |
最终打包成一个result(requester.go#L36-L47)投递到results通道。这套"生产者(worker)→ 管道(channel)→ 消费者(reporter)"模型,让压测与统计互不阻塞。
四、统计层:从原始结果到 Summary 报告
report.go:聚合器
requester/report.go 中的runReporter(report.go#L84-L114)在独立 goroutine 里循环消费results通道,累计各阶段耗时之和、状态码分布、错误分布。
snapshot()(report.go#L143-L215)在压测结束时对延迟切片排序,产出:
- 响应时间直方图:等宽分成 10 个桶(
histogram(),report.go#L237-L268) - 延迟分布:P10 / P25 / P50 / P75 / P90 / P95 / P99 七个百分位(
latencies(),report.go#L217-L235)
注意maxRes = 1000000(report.go#L31):明细数据最多保留 100 万条,超大压测下靠均值/直方图兜底,防止内存爆炸。
print.go:两种输出模板
requester/print.go 用 Go 的text/template定义了两套渲染模板(print.go#L96-L127):
- 默认 Summary:总量、最快/最慢/平均耗时、RPS、直方图、百分位、各阶段明细、状态码与错误分布
- CSV(
-o csv):每个请求一行,8 列指标,方便导入表格工具二次分析
文件头部的注释(print.go#L15-L34)对两种格式的字段解释得非常清楚,读代码时可直接当作文档用。
now_*.go:跨平台高精度计时
压测工具对计时精度极其敏感,因此now()做了平台拆分:
- now_other.go:其他平台直接用
time.Since,以包级startTime为零点 - now_windows.go:Windows 下调用
QueryPerformanceCounter系统调用,绕开time.Now()的微秒级精度瓶颈
这是 Go 构建标签(build tag)做平台差异化的典型用法。
五、一次压测的完整数据流
把各模块串起来,hey 的执行链路是:
- hey.go 解析参数 → 构造
http.Request→ 填充requester.Work Work.Run()启动 reporter goroutine + C 个 worker goroutine(requester.go#L119-L129)- 每个 worker 循环调用
makeRequest,带分段计时结果写入results通道 - reporter 实时消费、累计;worker 全部
Done后Finish()关闭通道 - reporter 排序、分桶、算百分位,
finalize()触发模板渲染,输出 Summary - 期间 Ctrl+C 或
-z到期 →Stop()向stopCh发 C 个信号 → workers 优雅退出
六、这套架构值得新手借鉴的 4 个设计点
- 入口与引擎解耦:hey.go 不含任何 HTTP 逻辑,requester 包可以脱离命令行独立使用(这也是 requester_test.go 能直接测引擎的原因)
- channel 解耦生产/消费:worker 只管发请求,reporter 只管统计,二者互不等待,压测吞吐不受统计开销影响
- 优雅停机:
stopCh信号 + 每轮循环检查,避免"发了 99% 的请求还停不下来" - 模板化输出:统计逻辑(report)与展示逻辑(print 模板)分离,加一种输出格式只需加一个模板串
七、如何上手阅读这份代码 🚀
建议的阅读顺序与 README.md 中的示例配合:
- 先在终端跑一遍
hey -n 100 -c 10 https://example.com,直观感受输出格式 - 从 hey.go 的
main()顺藤摸瓜到Work.Run() - 精读 requester/requester.go 的
makeRequest与runWorkers - 再看 report.go 的
snapshot(),理解直方图与百分位是怎么算出来的
如果想从源码构建,clone 仓库后执行make release即可生成三大平台的二进制(见 Makefile);日常开发只需go build。
hey 用不到 1500 行核心代码实现了生产级压测工具的完整能力——入口解析、并发引擎、分段计时、百分位统计、优雅停机——是学习 Go 并发与压测原理不可多得的精简范本。
【免费下载链接】heyHTTP load generator, ApacheBench (ab) replacement项目地址: https://gitcode.com/GitHub_Trending/he/hey
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考