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

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

作者:苏渡苇

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

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

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

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

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

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

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

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

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

列表如果按 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、equalshashCode。取字段写成 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、链路埋点感兴趣,欢迎看看这个还在打磨的小项目:

🔗 GitHubgithub.com/iweidujiang...

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

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

相关推荐
xcl09251 小时前
24小时自助健身房系统软件开发实战指南:从架构到部署
java·spring boot
ID34610744203 小时前
【课程设计】基于Spring Boot+Vue的游戏账号租赁系统的设计与实现-计算机毕设 附源码50345
javascript·vue.js·spring boot·python·node.js·php·课程设计
小蒜学长6 小时前
基于RFID技术的仓储管理系统设计(代码+数据库+LW)
java·数据库·spring boot·后端·rfid技术·仓储管理
xcl09256 小时前
健身场馆无人自动化系统:从架构设计到落地实践
java·spring boot
FYKJ_20107 小时前
【毕设分享】基于Web的校园兼职信息发布与申请系统07059
前端·vue.js·spring boot·mysql·typescript·spark·课程设计
雪芽蓝域zzs7 小时前
第 1 节:使用 IDEA 创建 SpringBoot4 Maven 项目
spring boot
晴空蓝天9 小时前
Spring Boot 3.5 脚手架里的 JWT + Redis 双轨会话,双端 token 隔离我是这么设计的
java·spring boot·redis
凤山老林9 小时前
RocketMQ 5.0 实战避坑与调优:事务、延迟、轨迹及高可用落地指南
spring boot·rocketmq
TanYYF9 小时前
Spring Boot 2 与 Spring Boot 3 自动装配的区别及 Boot 3 装配流程详解
spring boot