Hey 代码艺术:不到 1000 行 Go 代码如何设计一个生产级 HTTP 压测 CLI 工具
【免费下载链接】heyHTTP load generator, ApacheBench (ab) replacement项目地址: https://gitcode.com/GitHub_Trending/he/hey
hey是一款轻量级 HTTP 压测工具(HTTP load generator),常被用作 ApacheBench(ab)的现代替代品。它的全部核心逻辑不到 1000 行 Go 代码,却能完成并发压测、QPS 限速、分阶段延迟分析、直方图统计等生产级功能——是学习「如何用小体量代码写出可靠 CLI 工具」的绝佳范本。🎯
一、项目全景:5 个文件撑起一个压测工具
先建立整体认知。整个仓库只有少量源码文件,职责切分得非常干净:
| 模块 | 文件 | 职责 |
|---|---|---|
| 入口层 | hey.go(约 290 行) | 解析命令行参数,组装配置,启动压测 |
| 执行层 | requester/requester.go | Worker 协程池、请求发送、结果收集 |
| 统计层 | requester/report.go | 汇总指标、分位数、直方图 |
| 展示层 | requester/print.go | 模板渲染 Summary / CSV 报告 |
| 适配层 | requester/now_other.go、requester/now_windows.go | 跨平台时间基准差异 |
可以看到一个经典的三层架构:参数 → 执行 → 报告。想读懂任何 CLI 工具源码,先找这条主线,hey 就是最好的练习场。
二、设计一:参数到结构体的「单一配置对象」模式
在 hey.go 中,所有命令行参数(-n请求数、-c并发数、-qQPS 限速、-z压测时长等)全部先用标准库flag声明,再在main函数里一次性收敛成一个Work结构体(见 hey.go):
Work{ Request, N, C, QPS, Timeout, H2, ProxyAddr, Output, ... }这样做的好处是:入口层只负责"翻译",业务层只依赖一个结构体。Work可以被随意构造和注入,测试时不需要模拟命令行——requester/requester_test.go 里所有测试都是直接&Work{N: 20, C: 2}起服务开跑,代码极其简洁。
还有一个小巧思:重复出现的-H请求头,通过自定义headerSlice类型实现flag.Value接口(hey.go),让flag包原生支持「同一参数写多次」,无需引入任何第三方 CLI 库。
三、设计二:Channel 生产者-消费者,天然背压
并发压测最容易踩的坑是「协程池 + 全局切片」带来的锁竞争。hey 的选择是 Go 最地道的方案:
- 每个 Worker 每完成一个请求,把结果
*result投递到带缓冲的resultschannel(requester/requester.go); - 独立 Reporter 协程
for range轮询该 channel,边收边累加统计(requester/report.go); - 缓冲大小取
min(并发数×1000, 100万),用容量控制背压,防止慢消费拖垮生产端(requester/requester.go)。
生产者(Worker)与消费者(Reporter)完全解耦:Worker 不需要关心统计逻辑,Reporter 也不需要加任何锁。没有 mutex 的并发程序,天然少了一大类 bug。
四、设计三:httptrace 把一次请求拆成 5 段
普通压测工具只告诉你「平均 120ms」。而 hey 借助标准库httptrace.ClientTrace的回调(requester/requester.go),把每次请求拆成五个可独立测量的阶段:
- DNS 解析(DNS-lookup)
- TCP/TLS 建连(DNS+dialup)
- 请求写出(req write)
- 等待首字节(resp wait)
- 响应读取(resp read)
最终报告中每个阶段都有平均/最快/最慢三档统计(requester/report.go)。这意味着当用户抱怨「接口慢」时,能直接定位是 DNS 慢、建连慢还是服务端慢——这是它相比 ab 最有价值的差异化能力。
五、设计四:优雅停机的两条路径 🛑
压测工具必须随时能停。hey 在 hey.go 里用两个 goroutine 覆盖了两种停止场景:
- 用户按 Ctrl+C:
signal.Notify监听os.Interrupt,触发w.Stop(); - 达到
-z指定时长:time.Sleep到点后自动Stop()。
Stop()向stopCh发送C个停止信号,Worker 在每轮循环开头用select非阻塞检查停止位(requester/requester.go),实现「跑完当前请求即退出」的平滑收尾,最后Finish()关闭 results channel 并等待 Reporter 完成统计。停止不是中断,而是让所有组件有序地走完收尾流程——这是生产级与玩具级工具的分水岭。
六、设计五:模板驱动输出,一份数据两种报告
报告渲染放在 requester/print.go:Summary 报告和 CSV 导出都是text/template模板字符串,统计好的Report快照直接喂给模板执行。内置模板里甚至实现了纯文本直方图——用■字符按桶频率拉出条形图(requester/print.go),在终端里直观呈现延迟分布。
要新增 JSON 输出?加一个模板即可,统计层零改动。这正是「展示层独立」的红利。
七、设计六:零第三方依赖 + 构建标签的跨平台
翻一下 go.mod:核心仅依赖golang.org/x/net(为 HTTP/2 支持),没有 gin、没有 cobra、没有任何 CLI 框架。跨平台差异则由构建标签解决:now_other.go用time.Since,now_windows.go单独处理 Windows 计时基准,同一now()函数在不同系统编译出不同实现。
发布环节同样精简:Makefile 三行交叉编译产出 Linux/macOS/Windows 二进制,配 Dockerfile 供容器场景使用。
八、上手体验:安装与常用命令 🚀
想亲手感受这份代码的艺术,克隆仓库后交叉编译即可:
git clone https://gitcode.com/GitHub_Trending/he/hey make release常用命令一览(详见 README.md):
# 默认 200 请求 / 50 并发 hey https://your-api.example.com # 1000 请求、100 并发 hey -n 1000 -c 100 https://your-api.example.com # 持续压测 30 秒 hey -z 30s -c 50 https://your-api.example.com # 每 Worker 限速 10 QPS hey -q 10 -c 5 -z 30s https://your-api.example.com # 导出 CSV 供后续分析 hey -o csv -n 500 https://your-api.example.com跑完后你会看到总耗时、Requests/sec、延迟直方图、P10~P99 分位分布,以及前文提到的五段式延迟明细——全部来自那不到 1000 行代码。
九、总结:从 hey 身上提炼的 5 条 CLI 设计原则
把这篇文章浓缩成可复用的清单,写你自己的 Go CLI 工具时可以直接套用:
- 入口只翻译,不写业务:参数统一收敛进一个配置结构体,业务层对结构体编程(hey.go);
- 用 channel 代替锁:生产-消费解耦,Worker 无锁、可水平扩展(requester/requester.go);
- 测量要拆细:利用
httptrace等钩子做分阶段计时,报告才有诊断价值; - 停止要优雅:信号 + 时长双触发,每轮循环检查停止位,收尾再出报告;
- 输出走模板:一份数据快照喂多个模板,终端报告与 CSV 导出共享同一数据源(requester/print.go)。
代码少不等于简陋。hey 用不到 1000 行代码证明:清晰的职责分层、地道的并发原语和克制的依赖选择,就是最强大的生产级设计。下次写压测工具前,不妨先通读一遍 requester/ 目录——这大概是你今年能读到最短、却最值得精读的 Go 源码之一。
【免费下载链接】heyHTTP load generator, ApacheBench (ab) replacement项目地址: https://gitcode.com/GitHub_Trending/he/hey
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考