线上Go微服务调试需用dlv attach动态分析goroutine堆栈,pprof采样至少30秒并精准注册handler,OpenTelemetry需透传context至所有goroutine,服务端阻塞操作必须监听ctx.Done()。

线上微服务出问题,别急着重启或加机器——Go 程序跑着就能 debug,关键是要用对工具、走对路径。
如何 attach 正在运行的 Go 进程(不中断服务)
生产环境不能停服,dlv attach 是最直接的切入点。但前提是:编译时没加 -trimpath -ldflags "-w -s",否则变量名、行号全被 strip,断点设不上、print 看不到值。
- 先查进程 PID:
ps aux | grep your-service - 用 Delve 附着:
dlv attach 12345(12345 是 PID) - 进调试器后,用
goroutines列出所有 goroutine,再用goroutine 123 bt看堆栈,快速定位卡死或阻塞点 - 如果服务是容器化部署,需确保容器内已安装
dlv,且镜像构建时保留了调试符号(即不用CGO_ENABLED=0+-ldflags "-w -s"组合)
pprof 性能分析为什么“看了等于没看”
很多人开了 net/http/pprof 就以为万事大吉,结果采样 5 秒跑出来的 CPU profile 全是 runtime.mcall 或 syscall.Syscall,根本看不出业务瓶颈——因为采样时间太短,抖动没捕获,或 profile 被 GC/调度噪声淹没。
- CPU 分析至少跑
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30 - 内存分析优先看
inuse_space(当前占用)和alloc_objects(累计分配),避免只盯heap_allocs误判泄漏 - 别用
import _ "net/http/pprof"就完事——它会自动注册全部路由,生产环境有安全风险;应显式注册需要的 handler,比如只暴露/debug/pprof/heap和/debug/pprof/profile
日志里 trace_id 断链,下游调用变成新 trace
用了 OpenTelemetry 或 Jaeger,但日志里 trace_id 为空,或下游 HTTP 请求压根没带 traceparent header——这不是 SDK 配置错,而是 context 没透传到 goroutine。
立即学习“go语言免费学习笔记(深入)”;
- 检查所有
go func() { ... }()启动的协程:必须用ctx := ctx.WithValue(...)或更推荐的context.WithValue(ctx, key, val)显式携带,不能靠闭包捕获外层 ctx - gRPC 客户端调用必须用
ctx:如client.Do(ctx, req),否则 span 自动断开 - HTTP handler 中启动新 goroutine 时,别用
go handle(req),改用go handle(req.WithContext(ctx))
gRPC 服务端不响应超时,连接 hang 死十几分钟
客户端设了 context.WithTimeout(ctx, 5*time.Second),但服务端迟迟不返回,TCP 连接一直挂着,直到 keepalive 触发才断——这不是网络问题,是服务端没监听 ctx.Done()。
- 所有阻塞操作(DB 查询、HTTP 调用、channel receive)前,必须加
select判断ctx.Done() - 错误写法:
rows, err := db.Query(query);正确写法:rows, err := db.QueryContext(ctx, query) - 自定义 channel 操作要配合
ctx.Done():select { case v :=
线上调试最危险的不是不会用工具,而是忘了关——delve 进程长期挂着、pprof 路由没权限控制、trace exporter 把敏感字段打到 Jaeger UI,这些比 bug 本身更容易引发事故。










