先用 httptrace 定位卡点,再用 net.dialtimeout 和带延迟的 roundtripper 分段验证:httptrace 监控 dns/tcp/tls 各阶段耗时;net.dialtimeout 需配合独立 dns 查询测纯 tcp 延迟;自定义 roundtripper 注入可控延迟以验证超时逻辑。

直接结论:别测整条链路,先用 httptrace 定位卡点,再用 net.DialTimeout 和带延迟的 RoundTripper 分段验证。
用 httptrace 看清 HTTP 请求卡在哪一环
复杂链路下延时高,往往不是“整体慢”,而是某一段拖垮了全程。比如 DNS 解析卡 2 秒、TCP 连接被限速、TLS 握手失败重试——这些在 http.Get 返回错误前都看不到。
- 必须给
*http.Request注入httptrace.ClientTrace,不能只改http.Client;否则监听函数根本不会触发 - 重点关注
DNSStart/DNSDone(判断是否域名解析慢)、ConnectStart/ConnectDone(确认 TCP 是否建连成功)、GotConn(说明已拿到连接,后续延迟大概率在服务端或 TLS) - 如果
ConnectDone耗时长但GotConn立即触发,说明是服务端响应慢,不是网络问题;反之则要查客户端出口或中间代理
用 net.DialTimeout 单独测 TCP 建连延迟
HTTP 延时高,不等于网络延迟高。很多情况是 DNS + TCP 组合耗时,而你真正想测的是“到目标 IP 的三次握手时间”。
- 别对域名调用
net.DialTimeout("tcp", "example.com:443", timeout)—— 它会把 DNS 解析时间也算进去,结果失真 - 先用
net.DefaultResolver.LookupHost("example.com")单独测 DNS,再用net.DialTimeout("tcp", "192.0.2.1:443", 2*time.Second)测纯 TCP(IP 必须带端口,空字符串会 panic) - 每次调用都是新连接,不要复用
net.Conn;并发测试时加context.WithTimeout控制总耗时,避免端口耗尽
用自定义 RoundTripper 模拟可控延迟路径
线上链路可能经过 CDN、LB、Service Mesh,靠真实请求复现“500ms 延迟 + 2% 丢包”几乎不可能。此时需要在客户端主动注入延迟,验证超时逻辑是否健壮。
- 实现
RoundTripper接口,在RoundTrip方法开头加time.Sleep(delay),这样能精准触发context.DeadlineExceeded错误,而不是让服务端睡 —— 后者掩盖了客户端连接失败的真实路径 - 若目标服务启用了 HTTP/2,自定义
RoundTripper必须透传http2.Transport,否则可能出现连接复用失效或 panic - 延迟值建议通过 URL query 或 header 传入(如
?delay=300ms),方便不同测试用例切换,不用反复改代码
真正难的不是测出数字,而是确认这个延时属于哪一层:是本地 DNS 缓存失效?是出口 NAT 设备排队?还是远端服务 TLS 证书校验慢?每个环节的排查手段和修复方式完全不同。盯住 httptrace 输出的各阶段时间戳,比跑一遍全链路压测更有价值。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











