
Go HTTP handler里怎么加基础耗时埋点
直接在 http.HandlerFunc 里用 time.Now() 和 time.Since() 记录,别依赖中间件框架的“自动”统计——很多框架的耗时只算到 WriteHeader,漏掉 Write 或流式响应的真实发送时间。
- 必须在
defer前记录起始时间,否则 panic 时拿不到准确起点 - 日志或指标上报前检查
responseWriter是否已写入状态码(rw.(http.Hijacker)不重要,但rw.Header().Get("Content-Length")可能为空,别依赖它) - 如果用了
gzip中间件,压缩耗时会算进 handler 总耗时,这是对的;但若单独开 goroutine 写响应,time.Since就会低估真实延迟
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 包一层 ResponseWriter 拦截 WriteHeader/Write 调用
lw := &loggingResponseWriter{w: w}
next.ServeHTTP(lw, r)
log.Printf("%s %s %d %v", r.Method, r.URL.Path, lw.status, time.Since(start))
})
}
用 net/http/pprof 会拖慢线上接口吗
会,但只在你主动访问 /debug/pprof/ 路径时才触发采样;默认不开启 CPU 或 goroutine profile 的持续采集。问题出在「误配」:有人把 pprof.Register 加到所有请求里,或者用 runtime.SetMutexProfileFraction(1) 这类全局开关。
-
/debug/pprof/profile?seconds=30这种 CPU 采样会暂停所有 GPM 协程,30 秒内吞吐暴跌,绝对不能在高峰期调 - 内存 profile(
/debug/pprof/heap)是快照,不阻塞,但频繁抓取会导致 GC 压力上升 - 真正轻量的是
/debug/pprof/goroutine?debug=1,纯文本 dump,可每分钟 curl 一次做基线比对
OpenTelemetry Go SDK 怎么避免 context 传递污染业务代码
不是每个函数都该接收 context.Context;关键路径上只在 handler 入口、DB 查询、HTTP 调用这三处注入 span,其余内部逻辑用 otel.GetTextMapPropagator().Inject() 显式透传,别靠 context.WithValue 往下塞。
- 数据库驱动要换
go.opentelemetry.io/contrib/instrumentation/database/sql,原生database/sql不带 span - HTTP client 请求必须用
otelhttp.NewClient()包装,否则下游服务收不到 traceID - 别在 defer 里调
span.End()后还继续操作 response body——某些中间件(如 echo)会在 End 后清空 writer,导致 500
为什么 p95 延迟降不下来,但平均值看着挺好
因为慢请求集中在少数 endpoint 或特定参数组合,比如带模糊搜索的 /api/v1/items?q=xxx*,没走缓存又触发全表扫描;平均值被大量 20ms 请求拉低,掩盖了那 5% 的 2s 请求。
立即学习“go语言免费学习笔记(深入)”;
- 按 path + query 参数哈希分组统计 p95,而不是只看 method+path
- 检查
net/http.Server.ReadTimeout和WriteTimeout是否设得太宽(如 30s),让慢请求长期占着连接 - PostgreSQL 的
log_min_duration_statement = 100ms必须开,不然根本不知道哪条 SQL 在拖后腿
最常被忽略的是 DNS 解析和 TLS 握手——它们发生在 handler 执行前,但算在端到端延迟里;用 curl -w "@format.txt" -o /dev/null -s http://api.example.com 抓 time_namelookup 和 time_connect 才能定位。










