Hey压测工具result结构体深度解析:6个延迟字段如何读懂一次请求的一生
【免费下载链接】heyHTTP load generator, ApacheBench (ab) replacement项目地址: https://gitcode.com/GitHub_Trending/he/hey
跑完一次 hey 压测,屏幕上会刷出平均延迟、P99、resp wait 等一串数字,这些数字到底从哪来?答案就藏在 hey 压测工具里一个小小的result结构体中——它的 6 个延迟字段,完整记录了一次请求从发出到收到的全部旅程。本文带你逐层拆解 result 结构体:它如何诞生、6 个延迟字段如何测量,以及如何用它快速定位性能瓶颈。
先认识 hey:一个极快的 HTTP 压测工具
hey 是用 Go 编写的轻量级 HTTP 压测工具,可以看作 ApacheBench(ab)的现代替代。它的核心能力:
- 两个参数定压测规模:总请求数
-n、并发数-c - 实时统计:响应时间直方图、10%~99% 分位数分布、状态码分布
- 两种输出:默认人类可读的 Summary,或用
-o csv导出每个请求的明细
上手只需一条命令(更多用法见 README.md):
hey -n 1000 -c 100 https://example.comresult 结构体:一次请求的完整"病历"
hey 每发出一个请求,就会生成一条result记录,定义非常精炼,位于 requester/requester.go:
type result struct { err error statusCode int offset time.Duration duration time.Duration connDuration time.Duration // connection setup(DNS lookup + Dial up) duration dnsDuration time.Duration // dns lookup duration reqDuration time.Duration // request "write" duration resDuration time.Duration // response "read" duration delayDuration time.Duration // delay between response and request contentLength int64 }这些字段可以分成三组来看:
| 字段 | 归属 | 通俗解释 |
|---|---|---|
err、statusCode、contentLength | 请求元信息 | 这次请求成功了吗?返回什么状态码?响应多大? |
offset | 时间标记 | 请求开始时,压测已经运行了多久? |
duration | 延迟字段 1 | 整次请求的端到端总耗时 |
connDuration | 延迟字段 2 | 建立连接(DNS + TCP)花了多久 |
dnsDuration | 延迟字段 3 | DNS 解析花了多久 |
reqDuration | 延迟字段 4 | 发送请求体(写入)花了多久 |
delayDuration | 延迟字段 5 | 写完请求后,等多久才收到第一个响应字节 |
resDuration | 延迟字段 6 | 从首个字节到读完整个响应体花了多久 |
上面 6 个延迟字段就是本文的"主角",而offset更像一枚时间戳,标记请求的出生时刻。所有时间都以压测进程启动为零点,取时工具函数now()见 requester/now_other.go。
6 个延迟字段怎么测出来的:httptrace 钩子
hey 并不靠"猜"来得到这些耗时。秘密在makeRequest里的一组 httptrace 钩子:标准库的httptrace.ClientTrace会在 HTTP 请求的每个关键节点触发回调,hey 在每个节点精确记一次时钟。
| 回调钩子 | 覆盖阶段 | 记录的字段 |
|---|---|---|
DNSStart→DNSDone | DNS 解析前 → 后 | dnsDuration |
GetConn→GotConn | 申请连接前 → 后(仅新建连接) | connDuration |
GotConn→WroteRequest | 连接就绪 → 请求写完 | reqDuration |
WroteRequest→GotFirstResponseByte | 请求写完 → 收到首字节 | delayDuration |
GotFirstResponseByte→ 读完响应 | 首字节 → 响应体读完 | resDuration |
一个请求的完整生命周期,可以想象成一条直线时间轴:
开始 (offset) ├─ DNS 解析 (dnsDuration) ─┐ ├─ TCP 建连 (connDuration) ─┘ 仅新建连接时发生 ├─ 写请求 (reqDuration) ├─ 等待服务器处理 + 首字节 (delayDuration) ← 最关键 └─ 读响应体 (resDuration) 结束 (duration ≈ 以上全部相加)几个值得注意的细节:
- 复用连接时
connDuration为 0:keep-alive 下大多数请求复用同一条 TCP 连接,建连耗时只出现在新连接上,这正是判断连接复用是否生效的依据 dnsDuration是connDuration的子集:新建连接时,DNS 时间包含在建连时间内delayDuration代表"服务器时间":请求已发出,剩下的等待基本就是服务端在处理,是排查接口性能最关键的字段duration是端到端总耗时:复用连接时约等于 req + delay + res 三项之和
从单条记录到统计报告:Summary 里的 Details 区块
每条result是"一个人的病历",而 reporter 会把它们逐条累加:对 6 个延迟字段同时记录总和(算平均值)和逐条明细(算最快/最慢与分位数)。压测结束后,finalize 除以样本数得到均值,明细排序后得到各阶段的最大最小值。
Summary 输出中的 Details 区块正是这份统计的体现,每行格式为"平均、最快、最慢":
Details (average, fastest, slowest): DNS+dialup: 0.0123 secs, 0.0456 secs, 0.0000 secs DNS-lookup: 0.0045 secs, 0.0123 secs, 0.0000 secs req write: 0.0001 secs, 0.0003 secs, 0.0001 secs resp wait: 0.0345 secs, 0.2100 secs, 0.0089 secs resp read: 0.0056 secs, 0.0321 secs, 0.0012 secs5 行对应 5 个阶段字段,端到端总耗时duration则体现在 Summary 的 Average / Fastest / Slowest、响应时间直方图和分位数分布中。完整输出模板见 requester/print.go。
| 报告行 | 对应字段 | 偏高说明什么 |
|---|---|---|
| DNS+dialup | connDuration | 建连慢:DNS 慢、离服务器远、新连接多 |
| DNS-lookup | dnsDuration | DNS 解析是瓶颈:无缓存、DNS 服务器远 |
| req write | reqDuration | 请求体过大(大 body / 上传) |
| resp wait | delayDuration | 服务器处理慢——最该优先排查 |
| resp read | resDuration | 响应体过大或下行链路慢 |
CSV 模式:逐条查看每个请求的一生
想更精细地分析——比如找出哪个请求是"黑天鹅"——可以用-o csv把每个请求输出为一行 CSV,8 列与 result 字段一一对应,列定义说明就在 requester/print.go 的文件头部注释中:
hey -n 200 -c 20 -o csv https://example.com| CSV 列 | 对应字段 |
|---|---|
| response-time | duration |
| DNS+dialup | connDuration |
| DNS | dnsDuration |
| Request-write | reqDuration |
| Response-delay | delayDuration |
| Response-read | resDuration |
| status-code | statusCode |
| offset | offset |
配合offset列,还能回答更多问题:慢请求是开头并发洪峰造成的,还是中途某次服务器抖动?这正是"逐请求数据"的价值所在。
三个实战排障场景
用 6 个延迟字段,可以快速回答三个高频问题:
场景一:接口慢,是客户端还是服务器的问题?看resp wait(delayDuration)。若它占duration的大头,说明服务端处理慢,去查 SQL、下游依赖、GC;若它很小但resp read大,则是响应体问题(体积过大、未开压缩)。
场景二:为什么压测结果比线上明显慢?看DNS+dialup(connDuration)。偏大说明新连接多,可能是压测机离目标远、连接池没生效,或误开了-disable-keepalive——关掉再对比一次即可验证。
场景三:结果稳不稳,有没有偶发尖刺?看 fastest 与 slowest 的差距。fastest 10ms 而 slowest 500ms,说明问题不是"整体慢",而是"尾延迟高";再配合 CSV 的offset列,就能定位尖刺发生的时刻。
总结
hey 的 result 结构体 虽小,却是整个压测工具的数据骨架:
- 6 个延迟字段:
duration、connDuration、dnsDuration、reqDuration、delayDuration、resDuration - 采集机制:httptrace 钩子在每个阶段打精确时间戳
- 汇总输出:reporter 将逐条记录累加为平均值、最值、分位数与直方图
- 两种视角:Summary 看整体趋势,CSV 模式看单请求明细
下次压测报告里 resp wait 飙高时,你就不只是看到一串数字,而是清楚地知道:该去查服务器了。
【免费下载链接】heyHTTP load generator, ApacheBench (ab) replacement项目地址: https://gitcode.com/GitHub_Trending/he/hey
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考