我们如何衡量 HTTP 时延:DNS、TCP、TLS、TTFB 细分

发布: (2026年1月2日 GMT+8 00:55)
4 min read
原文: Dev.to

Source: Dev.to

Cover image for How We Measure HTTP Timing: DNS, TCP, TLS, TTFB Breakdown

当你检查网站的响应时间时,“847 ms”并不能告诉你太多。是 DNS 查询慢吗?网络延迟?还是服务器本身?你需要一个细分。

upsonar.io 我们会精确展示时间的去向。下面是我们使用 Go 的 net/http/httptrace 包进行测量的方法。

The problem

start := time.Now()
resp, _ := http.Get("https://example.com")
fmt.Println(time.Since(start)) // 847ms

847 毫秒——但为什么?它可能是请求生命周期中的任何部分。

解决方案

httptrace 钩住 HTTP 请求的每个阶段。您可以附加回调函数,它们会在每个阶段完成时触发:

trace := &httptrace.ClientTrace{
    DNSStart: func(_ httptrace.DNSStartInfo) {
        dnsStart = time.Now()
    },
    DNSDone: func(_ httptrace.DNSDoneInfo) {
        fmt.Printf("DNS: %v\n", time.Since(dnsStart))
    },
    // same pattern for TCP, TLS, etc.
}

req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

每个指标的含义

  • DNS Lookup – 将域名解析为 IP 地址。> 100 ms 通常表明 DNS 服务器问题或缓存未命中。
  • TCP Connect – 三次握手。主要受与服务器的物理距离影响。
  • TLS Handshake – 加密协商。典型值为 50–150 ms;> 300 ms 暗示服务器或证书链问题。
  • TTFB (Time To First Byte) – 包括 DNS、TCP、TLS 以及服务器处理时间。衡量响应速度的标准指标。
  • Transfer – 下载响应体。

工作示例

package main

import (
    "crypto/tls"
    "fmt"
    "io"
    "net/http"
    "net/http/httptrace"
    "os"
    "time"
)

func main() {
    url := "https://httpbin.org/delay/2"
    if len(os.Args) > 1 {
        url = os.Args[1]
    }

    var dnsStart, tcpStart, tlsStart time.Time
    totalStart := time.Now()

    trace := &httptrace.ClientTrace{
        DNSStart: func(_ httptrace.DNSStartInfo) { dnsStart = time.Now() },
        DNSDone:  func(_ httptrace.DNSDoneInfo) { fmt.Printf("DNS:  %v\n", time.Since(dnsStart)) },

        ConnectStart: func(_, _ string) { tcpStart = time.Now() },
        ConnectDone:  func(_, _ string, _ error) { fmt.Printf("TCP:  %v\n", time.Since(tcpStart)) },

        TLSHandshakeStart: func() { tlsStart = time.Now() },
        TLSHandshakeDone:  func(_ tls.ConnectionState, _ error) { fmt.Printf("TLS:  %v\n", time.Since(tlsStart)) },

        GotFirstResponseByte: func() { fmt.Printf("TTFB: %v\n", time.Since(totalStart)) },
    }

    req, _ := http.NewRequest("GET", url, nil)
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

    client := &http.Client{Transport: &http.Transport{DisableKeepAlives: true}}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

    transferStart := time.Now()
    io.ReadAll(resp.Body)
    fmt.Printf("Transfer: %v\n", time.Since(transferStart))
    fmt.Printf("Total: %v\n", time.Since(totalStart))
}

运行程序:

go run main.go https://httpbin.org/delay/2

示例输出

DNS:  148.00525ms
TCP:  142.56925ms
TLS:  289.685ms
TTFB: 2.956339583s
Transfer: 72.208µs
Total: 2.95670575s

TTFB(首字节时间)包括从发起请求到收到第一个字节的所有时间。Transfer 是下载响应体的时间——在这里响应体很小,所以总时间≈TTFB。

大响应示例

go run main.go https://proof.ovh.net/files/10Mb.dat

示例输出

DNS:  3.621333ms
TCP:  54.364208ms
TLS:  116.879041ms
TTFB: 286.073291ms
Transfer: 14.221007833s
Total: 14.507351083s

对于 10 MB 的文件,Transfer 在整体延迟中占主导。

注意连接复用

Go 的 http.Client 默认会复用连接。对同一主机的后续请求会因为连接已经建立而显示 DNS、TCP 和 TLS 为 0 ms。

为了获得准确的测量,请禁用 keep‑alives:

client := &http.Client{
    Transport: &http.Transport{
        DisableKeepAlives: true,
    },
}

什么是慢速数字告诉你的

  • DNS > 100 ms → 尝试更快的 DNS 提供商(例如 Cloudflare、Google)。
  • TCP > 100 ms → 服务器离用户较远;考虑使用 CDN。
  • TLS > 200 ms → 检查证书链;启用会话恢复。
  • TTFB > 500 ms → 后端瓶颈:数据库慢、冷启动、重处理。
  • Transfer high → 负载过大;启用 gzip/Brotli 压缩。

200 ms 规则

用户会注意到约 200 ms 以上的延迟。如果你的 TTFB 单独就超过了这个阈值,无论前端如何优化,页面都会感觉很慢。

使用免费、无需注册的工具在你自己的网站上试一试:upsonar.io/tools/diagnose.

Back to Blog

相关文章

阅读更多 »

TCP 不知道什么是消息

当我在使用 HTTP 时,我心里一直有一个潜在的假设: > 如果我发送一个东西,另一端就会收到一个东西。 这看起来很显然。Al…