如何用链路追踪快速定位线上慢请求
定位线上慢请求的最快路径是:先拿到一个慢请求的 traceID,在链路追踪系统里看瀑布图找出 self time(自身耗时,即 span 总时长减去所有子 span 时长)最大的那个节点,再用它的标签下钻到具体 SQL、下游接口或本地线程状态。熟练之后单条链路排查在 3 分钟内完成,比翻日志 grep 快一个量级。
链路追踪定位慢请求的整体步骤是什么?
结论:四步——取 traceID → 看瀑布图 → 算 self time 找瓶颈 span → 用 span 标签和同 traceID 日志下钻。
第一步是拿到一条真实慢请求的 traceID,而不是凭感觉猜。第二步打开瀑布图,从根 span 往下看,把每个节点的 total time 和 self time 都算出来。第三步锁定 self time 占比超过 50% 的那个节点,它就是这条链路的瓶颈。第四步点击该 span,看它的 attributes,比如 db.statement(执行的 SQL 语句)、peer.service(下游服务名)、http.url,再跳到对应的日志或 SQL 平台确认。
这四步是串行的,任何一步跳过都会退化回「凭经验猜」,那就不叫定位了。
怎么拿到一个慢请求的 traceID?
结论:三个入口——响应头透传、网关访问日志、按耗时排序的接口列表。
如果你的服务遵循 W3C Trace Context 标准,响应头里会带 traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01,其中第二段就是 32 位 traceID。前端报障时让他打开浏览器 DevTools 复制这行,是最快的路径。
后端场景在 Nginx 加一行 log_format main '$remote_addr "$request" $status $request_time $http_traceparent';,把网关日志接到链路系统里,就能按 $request_time 排序取 topN 慢请求。
第三个入口最常用:在 APM 的接口列表页按 P99 排序,点进某条慢调用记录,直接拿到 traceID。注意这时候要确认采样策略——如果采样率是 1%,慢请求可能刚好没被采到。结论:生产环境建议对耗时超过 500ms 的请求做尾部采样(tail-based sampling,按请求结果决定是否上报),保证慢请求 100% 落库,正常请求按 1% 采样,这样存储成本可控又不丢样本。
瀑布图里应该看哪个 span?
结论:看 self time 最大的 span,不是看总时长最大的 span。
父 span 的总时长包含了子 span,所以耗时最长的那条链路最顶层节点永远「最慢」,但它往往不是问题根源。判断方法是:某个 span 的 self time 占整条 trace 总时长的比例超过 50%,它才是瓶颈。
常见的三类罪魁和对应标签:
数据库慢:span 名类似 mysql.query 或 SELECT orders,看 db.statement 拿到完整 SQL,再看 db.rows_affected。如果返回行数上万,基本是缺索引或没分页。
下游 RPC 慢:span 上有 peer.service=user-service、rpc.method=getUserProfile,说明慢在对端。此时不要继续在当前服务查,直接拿这条 traceID 去下游服务的链路系统里搜索同一条链路,看是哪一跳断掉的。
没有子 span 的空白:父 span 时长 800ms,两个子 span 分别 100ms、120ms,中间空出 580ms 没有任何子节点。这 580ms 是本地开销,排查方向是线程池排队、锁竞争、GC 停顿、JSON 序列化。具体动作:看该时间点前后的 GC 日志,或对进程做 3 次 jstack 取线程快照,比对哪些线程卡在 BLOCKED 状态。
埋点不全导致链路断掉怎么办?
结论:链路断点通常来自三个位置——JDBC、HTTP 客户端、消息队列,缺一个就会出现「黑洞 span」。
排查时看到某个 span 时长远超它的子节点总和,就说明中间有未被埋点的调用。补法很直接:Java 应用引入 OpenTelemetry 的 javaagent,加启动参数 -javaagent:opentelemetry-javaagent.jar,它会自动注入 JDBC、OkHttp、Kafka、Redis 的埋点,不用改业务代码。
另一个必要动作是日志关联 traceID。在 logback 的 pattern 里加 %X{traceId},并在 MDC 中写入当前 traceID,这样从链路跳到日志平台时可以用 traceID 一次捞出这条请求的全部日志,不用再靠时间戳猜。
一次完整的慢请求排查过程是怎样的?
结论:从告警到定位根因,正常在 3 到 5 分钟内完成。
假设订单接口 P99 从 200ms 涨到 1.8s。第一步,在接口列表按 P99 排序,点开最慢那条 trace,拿到 traceID。第二步,瀑布图显示根 span 1.78s,下面挂三个节点:Redis 查询 12ms、MySQL 查询 1.6s、下游风控服务 90ms。第三步,self time 占比最高的是 MySQL 那个 span,占 89%。第四步,展开它的 db.statement,发现是一条 SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC,db.rows_affected 显示返回 4.2 万行。结论很明确:这个查询没走 (user_id, created_at) 联合索引,也没加 LIMIT。
加上索引 ALTER TABLE orders ADD INDEX idx_user_created (user_id, created_at DESC); 并补上 LIMIT 20 之后,该 span 降到 8ms,接口 P99 回到 210ms。
要点收束:整条排查链是「traceID → 瀑布图 → self time 占比 → span 标签 → 具体 SQL 或线程状态」,中间任何一次「我觉得应该是 XX 慢」的猜测都要用 span 数据证伪。把 traceID 打进日志、给慢请求开尾部采样、补齐 JDBC 和 HTTP 客户端埋点,这三件事做完,慢请求定位才算真正具备工程效率。
原文链接:https://www.gj0.com/thread-225.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。