Spring Insight 里如何把 Span 收成一条链路

Spring Insight 里如何把 Span 收成一条链路

作者:苏渡苇

项目地址 :github.com/iweidujiang...(感谢 Star !)

一次下单接口,在链路追踪只占列表一行?

上一篇把拓扑讲完了:图上的箭头来自 remoteService,按 服务对 归边。

链路页要回答的是另一件事------一次请求从头到尾经过了哪些调用。

如果把内存里的 Span 直接摊成表格,同一条 Trace 会占很多行,入口、总耗时、成没成功都看不清。

控制台现在的做法是:同一 traceId 收成一行摘要,点进去才展开那些 Span。

这篇要讲的就是这行摘要:根服务、入口操作、总耗时、是否有错误,是怎么从扁平记录里抠出来的。

列表要的是摘要,不是把 Span 全铺出来

一次跨服务调用会留下多条 Span:Gateway 进站、Gateway 出站、order 进站、order 调 user、order 调 product......它们共享同一个 traceId,parentSpanId 把树串起来。

列表如果按 Span 展示,刷新一次能刷出几十上百行,其中很多是同一次下单。打开页面的人想找的通常是:刚才那次请求慢不慢、有没有 ERROR、入口是哪个服务。

所以列表接口返回的不是 Span 数组,而是 Trace 摘要:

字段 含义
traceId 这一次请求的 ID
serviceName 根 Span 所在服务,当作入口
operationName 根 Span 的操作名
startTime 这棵树里最早的开始时间
durationMs 最晚结束减去最早开始,整段墙钟时间
spanCount / serviceCount 这条 Trace 里有多少条 Span、经过几个服务
hasError 其中任意一条失败,整行就算 ERROR

聚合代码在此:TraceSpanPersistenceService.getRecentTraceSummaries

查询接口:GET /api/v1/ui/traces/recent

进站 SERVER、JDBC 只要挂了同一个 traceId,都会进 spanCount,并不要求带 remoteService。拓扑那张图会丢掉没有远端名的记录,列表这边不会。

用 record 把同一条 Trace 累在一起

扫描仍在那把锁里做,按 traceId 放进 LinkedHashMap,每条 Trace 一个累加器:

java 复制代码
record Acc(
        String traceId,
        long minStart,
        long maxEnd,
        int spanCount,
        boolean hasError,
        String rootService,
        String rootOperation,
        LinkedHashSet<String> services
) {}

record 是 Java 16 正式引入的一种紧凑型类声明 ,专为纯数据载体设计,可参考我之前的这篇介绍:record + sealed class = Java 的"类型乐高"!

这里用它,是因为 Acc 只扛一组数字和字符串,不需要再手写 getter、equals、hashCode。取字段写成 acc.traceId()、acc.minStart(),不像普通 JavaBean 那样 getXxx()。Insight 要求 JDK 21,这类写法可以直接用。

碰到一条 Span,先用时间窗口切掉过旧的,再更新这几个量:

  • minStart / maxEnd:整段链路的起止,缺 endTime 时用 startTime + durationMs 顶上
  • spanCount:条数加一
  • services:出现过的 serviceName 去重
  • hasError:任意一条 statusCode 为 ERROR,或 success == false,整行打上错误
  • 根节点:parentSpanId 为空的那条,记下它的服务名和操作名,当作入口
java 复制代码
boolean root = s.getParentSpanId() == null || s.getParentSpanId().isBlank();

总耗时不是把每条 Span 的 durationMs 加起来------子调用是嵌套、并行都有,加总会虚高。这里用 maxEnd - minStart,表示这次请求从最早埋点到最晚结束跨了多久。

扫完之后再过滤、排序:按 startTime 倒序,截到 limit。根服务如果始终没找到(整棵树都带了 parent),就退回这条 Trace 里出现过的第一个服务名,避免列表上入口是空白。

筛选项对着接口参数来

接口参数和页面上的筛选项是对齐的:

text 复制代码
GET /api/v1/ui/traces/recent
    ?hours=24
    &limit=100
    &service=sca-order
    &status=error
    &q=create
    &minDurationMs=500

service 看的是这条 Trace 有没有经过 该服务,不是只匹配入口。

order 调了 user,按 sca-user 筛,这次下单仍会留下。status=error|ok 看的是整行的 hasError。

关键字会拿 Trace ID、入口操作、入口服务、以及路过的服务名做忽略大小写包含。

minDurationMs 用的是上面算出来的墙钟耗时,仪表盘跳慢请求也走这个参数。

这些条件都在内存里聚完后过滤,没有单独的索引。前端把当前筛选写进地址栏,复制 URL 能打开同一组条件;点某一行再请求 GET /api/v1/ui/traces/{traceId},那才是这条 Trace 下的 Span 明细,瀑布图怎么画放在下一篇。

收成一行之后,还要看清哪一段慢

链路列表把同一 traceId 收成一行:入口来自没有父节点的根 Span,总耗时是整段墙钟,任意一条失败整行就是 ERROR。接口是 GET /api/v1/ui/traces/recent,筛选在聚完之后做。

看列表时有两处容易对不上预期。总耗时不是子 Span 耗时之和,并行调用加起来会比墙钟更夸张;按服务筛选是 经过该服务,不是只显示以它为入口的请求。根找不到时会拿出现过的第一个服务名顶上,跨进程如果 parent 没带上,入口可能不准。

点进某一行之后,还要把这些 Span 按开始时间和父子关系排成瀑布,才能看出哪一段把时间吃掉了。

下集预告:详情页的时间线怎么把扁平 Span 画成调用树,深度、偏移和条宽分别从哪几个字段来。

最后:欢迎围观 Spring Insight

如果你对轻量监测、Spring Boot Starter、链路埋点感兴趣,欢迎看看这个还在打磨的小项目:

🔗 GitHub :github.com/iweidujiang...

当前形态很朴素:业务服务加 spring-insight-agent-starter,旁边单独跑 insight-server 看拓扑和链路。能力有限,代码也还糙,适合当练手和对照。

你的 Star 是对我最大的鼓励。

相关推荐
Q264336502328 分钟前
【有源码】基于Spring Boot和Vue的在线考试与交流系统-教育信息化背景下在线考试与交流一体化平台
java·vue.js·spring boot·mysql·spring·毕业设计·课程设计
EatFan1 小时前
2026 后端 AI 集成三条落地路径:Spring AI 2.0、MCP 协议与旁路服务怎么选
java·人工智能·spring boot·spring·大模型·spring ai·mcp
imDwAaY2 小时前
Spring Boot的核心配置文件一览及其加载顺序
java·spring boot·后端
专业程序开发源3 小时前
springboot校园志愿者管理系统28562-计算机课程设计、毕业设计
java·spring boot·后端·python·django·php·课程设计
Wang's Blog15 小时前
Java框架 SpringCloud 快速入门: 实现 Feign 最佳实践(抽取方式)
java·spring cloud
Wang's Blog20 小时前
Java框架 SpringCloud 快速入门: Feign 的性能优化
java·spring cloud·性能优化
vx-程序开发1 天前
【计算机毕设】51300童装超市管理系统的设计与实现
java·vue.js·spring boot·源码·课程设计·毕设·大作业
ym hyd 1111 天前
题库建立与考试系统源码 Java+SpringBoot+Vue3 前后分离
java·开发语言·vue.js·spring boot·毕设
Wang's Blog1 天前
Java框架 SpringCloud 快速入门: Feign 的自定义配置与日志级别
java·开发语言·spring cloud
萧瑟余晖1 天前
Spring Boot Starter机制与自定义详解
spring boot