SpringBoot集成Skywalking:链路跟踪与APM性能监控实战
2026/9/9 19:56:17 网站建设 项目流程

1. 先说痛点:线上服务出了问题,你花了多久才找到根因?

我印象很深的一次经历:某个下午,线上订单服务突然变慢,用户反馈下单要转圈十几秒。我们几个后端围着日志查了快一个小时——服务A的日志显示调了服务B,服务B的日志显示自己只花了200ms,但服务A却等了4秒。两边各执一词,数据库也没有慢SQL,缓存命中率也正常。最后实在没辙,只能用最笨的办法:在A和B之间临时加耗时日志,重新发布,再复现一遍。

如果你也经历过这种“明明每个环节都正常,但整体就是慢得离谱”的排查噩梦,那你大概率需要一套链路跟踪系统。这篇文章就聊我在SpringBoot项目里集成Skywalking的全过程,从最基础的概念、环境搭建、项目接入,到实际排障的用法和踩坑心得,适合刚接触链路跟踪、或者已经用过但没深入折腾过的同学。

Skywalking是国内开源圈子里使用率非常高的一款APM(应用性能监控)工具,核心功能就是链路跟踪、服务拓扑分析、慢SQL追踪。相比同类工具,它的最大优势是对业务代码无侵入——Java项目只需要加上探针参数启动,就能自动采集调用链数据,不用改一行业务代码。这期教程主要讲解怎么把Skywalking和SpringBoot项目集成起来,让你在浏览器里看到一次请求从入口到数据库的完整旅程。

2. 选型上的门道:为什么是Skywalking,而不是Zipkin或Jaeger

先解决一个大家都会问的问题:链路跟踪工具那么多,Zipkin、Jaeger、Skywalking到底怎么选?我不是说其他工具不行,而是它们解决的侧重点不太一样。

2.1 三类工具的典型对比

  • Zipkin:老牌链路跟踪系统,Twitter开源。特点是轻量、实现简单,很多中间件原生支持上报数据。但它的UI比较朴素,自带功能偏少,更像个“链路查询器”,缺少告警、指标聚合这些偏运维的能力。
  • Jaeger:CNCF孵化的项目,师承Google Dapper论文,在云原生环境里很吃香。如果你用的是Kubernetes + Istio这类Service Mesh架构,Jaeger几乎是标配。不过它对非云原生场景的友好度一般。
  • Skywalking:国产开源,Apache顶级项目。最大的卖点是Agent自动埋点——Java生态里主流的框架、中间件、数据库驱动几乎全覆盖,不需要你手动埋点就能拿到数据。而且它自带拓扑图、告警、JVM监控、日志关联,功能一体化程度很高。

2.2 我的选型理由

我自己在中小型微服务团队里做技术选型时,核心考量有四个:

  1. 接入成本:Zipkin和Jaeger在SpringBoot里接入需要主动引入依赖、配置spring.sleuth相关参数,并且很多场景还要手动写Span。Skywalking只需要改启动脚本,业务代码零改动,对一个存量项目来说,这个优势是压倒性的。
  2. 功能完整度:Skywalking不只是链路跟踪,它还能展示服务之间的依赖拓扑、每个接口的响应时间分布、JVM内存/GC情况,甚至SQL执行参数。一套系统解决监控和追踪两个问题,省事。
  3. 中文社区和文档:这个不用多说,Skywalking的中文资料、博客、社区讨论非常丰富,遇到问题搜索一下基本都有答案。工具再强,没人答疑也费劲。
  4. 部署和维护成本: Skywalking服务端(OAP Server + Web UI)就是两个进程,没有额外依赖(存储默认用H2文件,也可以接ES、MySQL)。相比之下,Zipkin如果想做持久化还得配ES,Jaeger一般也要搭Cassandra或ES。

注意:如果你所在团队已经重度使用Kubernetes + Istio,Jaeger会更契合云原生链路;但如果你主要是SpringCloud、SpringBoot传统微服务,Skywalking的性价比是最高的。

2.3 理解Skywalking的三个核心概念

在动手部署前,先把三个词搞明白,否则后面很多配置你会看得一头雾水:

  • Agent(探针):一个挂在应用进程里的JAR包,负责自动采集调用链数据,然后上报给OAP Server。它和业务代码完全隔离,这也是Skywalking“无侵入”的关键。
  • OAP Server(Observability Analysis Platform):分析引擎,接收Agent上报的数据,做聚合、存储、告警计算,并对外提供查询接口。通俗理解就是后端服务。
  • SkyWalking UI:Web展示界面,从OAP Server拉数据,把链路、拓扑、指标可视化。类似数据库管理工具和数据库的关系。

这三者的关系可以这样类比:Agent是放在每个商家收银台旁边的“监控摄像头”,OAP Server是汇总所有监控录像并做分析的“机房”,UI就是给你看录像回放的“显示屏”。

3. 环境准备:搭建Skywalking服务端(含版本选择)

Skywalking的部署方式有很多种:源码起、二进制包起、Docker起、K8s helm起。为了方便演示和日常开发联调,我推荐用Docker Compose快速搭建,简单、干净、可重复。但正式环境如果是生产级别,建议单独部署OAP并把存储切到Elasticsearch。

3.1 版本选择细节

我这次使用Skywalking 8.6.0,对应Agent版本也是8.6.0。这里有个非常关键的匹配规则:Agent和OAP Server的版本号要尽量保持一致。小版本不一致可能能跑,但大版本不一致基本连不上或者数据解析出错。我踩过Agent 8.7.0配合OAP 8.6.0的坑,上报数据一直看不到,查了半天是版本兼容问题。

另外要注意JDK版本的适配:Skywalking 8.6.0的OAP Server要求JDK 8+,而Agent对JDK8-11的支持都挺稳。如果你的SpringBoot项目是JDK17,建议用Skywalking 9.x以上的版本,兼容性更好。

3.2 Docker Compose快速搭建

我准备了一个最简配置,放到项目里的skywalking-docker-compose.yml文件:

version: '3' services: # Skywalking OAP Server oap: image: apache/skywalking-oap-server:8.6.0-es7 container_name: skywalking-oap restart: always ports: - "11800:11800" # Agent上报数据的gRPC端口 - "12800:12800" # UI查询数据和请求OAP的HTTP端口 environment: SW_STORAGE: h2 # 先拿H2内存存储跑起来,演示够用 volumes: - ./oap-data:/tmp/skywalking-data # 持久化H2数据文件,重启不丢 # Skywalking UI ui: image: apache/skywalking-ui:8.6.0 container_name: skywalking-ui restart: always depends_on: - oap ports: - "8080:8080" # 本地8080端口访问UI,如果被占用改成18080:8080 environment: SW_OAP_ADDRESS: http://oap:12800

在本机执行:

docker-compose -f skywalking-docker-compose.yml up -d

等一两分钟,访问http://localhost:8080,如果能看到SkyWalking UI的登录页,说明服务端起来了。

注意:生产环境不要用H2存储,数据量上来后会有性能问题。建议SW_STORAGE=elasticsearch并单独部署ES集群。网络热词里提到“docker部署springboot项目”时也常配合Skywalking容器一起编排,如果是云环境就用docker stack或K8s管理更合适。

3.3 端口说明与网络规划

很多同学第一次部署容易混淆端口角色,我列个表方便以后查:

端口所属进程用途注意事项
11800OAPAgent上报链路数据的gRPC端口Agent配置里填这个端口
12800OAPUI查询数据用的HTTP端口一般调试接口时才用
8080UISkywalking前端页面可映射成宿主机其他端口

如果公司网络策略严格,需要保证应用服务器能访问OAP的11800端口,UI那台机器能访问12800和8080端口。我遇到过明明UI能看到数据,但Agent一直不上报的情况,排查到最后发现是11800端口在安全组里没放通。

4. SpringBoot项目接入:两分钟完成Agent配置

服务端就绪后,接下来把SpringBoot项目接入Skywalking。这是整篇教程里最爽的一步,因为不需要pom里加任何依赖,不需要写任何配置类,只需要改启动命令。

4.1 下载Agent包

首先到Skywalking官网下载对应版本的Agent发行包,通常在agent目录下会有这些内容:

  • skywalking-agent.jar:Agent的核心入口JAR
  • config/agent.config:Agent配置文件
  • plugins/:存放各种框架的自动埋点插件
  • optional-plugins/:部分可选插件,如Spring Cloud Gateway、Webflux等

我习惯把Agent包解压到固定目录,比如/opt/skywalking-agent,这样多个项目可以复用同一份。

4.2 配置Agent启动参数

启动SpringBoot应用时,在JVM参数里加两行:

java -javaagent:/opt/skywalking-agent/skywalking-agent.jar \ -Dskywalking.agent.service_name=order-service \ -Dskywalking.collector.backend_service=127.0.0.1:11800 \ -jar order-service.jar

参数含义:

  • -javaagent:指定Agent JAR路径,让JVM在加载业务类之前先加载探针。
  • -Dskywalking.agent.service_name:给这个服务取名,会显示在UI的拓扑图和表里。建议和SpringBoot应用名保持一致,比如spring.application.name的值。
  • -Dskywalking.collector.backend_service:OAP Server的gRPC地址和端口,格式是ip:11800

agent.config文件里也有同样含义的配置项。命令行参数优先级更高,所以我没有直接改文件,而是通过-D传参。如果你用的是IDEA本地调试,可以在VM options里加上这些参数。如果服务是通过Docker部署的,就在docker run命令里增加JAVA_OPTS环境变量。

4.3 验证Agent是否生效

启动项目时,观察控制台日志。如果看到类似下面的输出,说明探针已经成功挂载:

SkyWalking agent [order-service] start successfully.

如果没有看到,检查一下-javaagent路径是否正确。日志级别不够时,也可以加-Dskywalking.logging.level=debug看详细加载过程。

然后随便调用两个接口,再回到UI页面,服务列表里应该能看到order-service。点击进入后,能看到接口的请求量、P95耗时、成功率等指标。再点击某个接口的Trace按钮,就能看到这次请求的完整调用链。

4.4 为什么Agent能做到无侵入?

这里顺带解释一下原理,理解了才能用好。Skywalking Agent本质上是一个Java Agent,利用JDK 1.5提供Instrumentation机制,在类加载的时候动态修改字节码,给目标类的方法前后插入埋点逻辑。这个过程发生在应用代码运行之前,所以业务代码不需要感知。

你可以把Agent想象成给电梯装的计费芯片:电梯本身的控制程序不用改,芯片自动感应每一层进出的人,记录谁在几楼停了多久。探针做的事类似,它自动“感应”Tomcat的请求入口、RestTemplate的调用、JDBC的SQL执行,把这些数据串成一条链路。

5. 进阶实战:手动埋点、跨线程传参和MQ链路透传

自动埋点覆盖了绝大多数常见场景,但总有一些特殊场景需要手动介入。比如异步线程里发起的调用,Skywalking默认会丢失上下文——因为链路ID存在ThreadLocal里,子线程拿不到父线程的数据。

5.1 用apm-toolkit手动埋点

引入依赖:

<dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-trace</artifactId> <version>8.6.0</version> </dependency>

然后在你关心的业务方法上加注解:

@Trace public void doBusiness(String orderId) { // 业务逻辑 }

这样这个方法会成为链路里的一个Span,能单独看到耗时。如果你还想在链路里带上业务参数,可以在方法内部调用:

ActiveSpan.tag("orderId", orderId); ActiveSpan.info("开始处理订单业务");

加了自定义标签后,在UI里查看某条Trace的详情时,就能直接看到orderId的值,排障时非常管用。比如用户反馈某个订单一直失败,你搜到对应耗时长的Trace,直接在Span信息里看清楚是哪笔订单,不用再去翻日志把TraceID和订单号关联起来。

5.2 异步线程场景的处理

如果业务代码里用了ExecutorServiceCompletableFuture,或者用了@Async注解,子线程里的调用默认不会被串到父链路里。解决办法是使用Skywalking提供的包装类:

ExecutorService executorService = Executors.newFixedThreadPool(10); executorService.execute(CallableWrapper.of(() -> { // 这里会继承主线程的Trace上下文 remoteService.call(); }));

对应还有RunnableWrapperSupplierWrapper,包在任务外层即可。原理是在任务执行前临时把父线程的Context快照塞到子线程的ThreadLocal里,任务执行完再清理掉,避免内存泄漏。

5.3 MQ场景的链路透传

在订单系统里,经常是下单接口把消息发到RocketMQ/Kafka,消费者再处理。自动插件对MQ也有支持,但默认只透传Header里的链路上下文。如果你用的是Spring Cloud Stream或自定义消息体,需要额外设置消息头透传。

以RocketMQ为例,Skywalking 8.6.0插件会自动把sw8开头的链路上下文放进消息的Properties里,消费者端取到后自动恢复上下文。这要求消息中间件允许设置自定义属性。如果用的某些封装框架把Properties丢了,只能自己手动写ContextManager来跨线程传递,不过这个情况比较少见,遇到再深入排查即可。

6. 在Skywalking UI里做链路分析(含实际排障流程)

集成跑通之后,最关心的就是怎么利用这些数据排查线上问题。我分享一个真实的排查过程给大家参考。

6.1 拓扑图与服务依赖透视

在UI首页的拓扑图模块,能看到所有接入服务以及它们之间的调用关系。线条的粗细代表调用量大小,颜色代表健康状态,红色说明有异常。有一次我发现user-serviceorder-service之间有一根很粗的红线,点进去查看,发现order-service调用user-service接口的失败率高达30%。顺着这个线索查下去,才发现是上游接口在高峰期频繁超时。

如果你们系统没接Skywalking,这种调用量级的异常往往要靠业务监控告警才能发现,而且定位慢。有了拓扑图,一眼就能看出来问题出在哪条边上。

6.2 链路详情:从TraceID到瓶颈定位

用户反馈某个请求很慢时,在Skywalking UI的追踪页面,输入TraceID(可以从日志里搜到)即可查看完整调用链。Skywalking把每次请求拆成若干个Span,每个Span记录了一个调用动作,比如HTTP调用、SQL执行、Redis操作,并标记耗时。

我之前排查过一个“下单接口偶尔耗时3秒”的问题。从Trace详情里看到,耗时集中在一次SELECT上,但那条SQL如果单独在数据库客户端执行,只有几十毫秒。继续下钻发现,那次查询前正好发生了连接池等待——连接池里的连接都被长时间占用,新请求只能排队等连接。之后再排查具体是什么SQL长时间占用连接,最终定位到是统计报表的批量查询拖垮了连接池。如果没有链路数据,这种场景几乎不可能靠猜定位到。

6.3 慢SQL与数据库端点分析

Skywalking还能自动抓取SQL执行信息。在UI的数据库模块,能看到每条SQL的调用次数、平均耗时、最大耗时。对慢SQL点进去,会直接显示带参数的完整SQL语句,省去你自己抓慢日志、拼参数的麻烦。

这套功能尤其适合没有专职DBA的团队。我用它发现过不少隐藏问题,比如:某条SQL在测试环境数据量小、跑得飞快,上了生产后数据量大了,命中索引差,平均耗时飙升到几百毫秒。SQL执行参数被Skywalking记录下来了,直接拿去生产库EXPLAIN分析执行计划,很快就找到了缺失的联合索引。

7. 常见问题速查:版本匹配、数据不上报、UI空白等

集成过程里,我前前后后踩了不少坑。整理成一张速查表,每个问题都是亲身遇到或帮别人排查过的:

问题现象常见原因解决方式
项目启动报SkyWalking agent start successfully但UI看不到服务Agent版本和OAP版本不匹配升级/降级到同版本,重新挂载启动
UI能看到服务,但看不到Trace数据探针挂载成功但没有触发埋点;或采集器端口不通检查是否调用了自动插件支持的框架;telnet测试11800端口连通性
微服务A调B,但链路只显示A自己的Span没有在B上也挂Agent;或B的service_name配置错误在B的JVM参数中加入同样的-javaagent
请求在异步线程里链路断裂子线程未继承Trace上下文RunnableWrapper/CallableWrapper包装
UI打开是空白页UI服务连不上OAP的12800端口检查SW_OAP_ADDRESS配置,以及两个容器的网络是否在同一网络
链路里看不到SQL某些ORM框架插件没生效查看plugins目录是否有对应框架插件;必要时使用optional-plugins/apm-spring-webflux
数据量大导致OAP内存飙升存储选型不对或Agent采样率太高生产切ES存储;调整SW_AGENT_SAMPLE_RATE采样率参数

7.1 关于SpringBoot版本太高的兼容问题

网络热词里提到“springboot版本太高”,这里单独说一下。SpringBoot 2.x和Skywalking 8.x配合基本没有大坑,但SpringBoot 3.x基于Spring Framework 6和Jakarta EE规范,很多字节码增强的插件出现兼容性问题。如果你用的SpringBoot 3.x,建议直接用Skywalking 9.x或最新版,官方在9.x之后对SpringBoot 3做了更多适配。这个梗几乎每隔一段时间就有人在社区问,提前避坑。

7.2 Agent对性能的损耗实测

不少人对探针的性能损耗有顾虑。我做过一次简单的压测对比:在同样的接口上,挂不挂Agent,P99耗时的差异大概在3%-5%,吞吐量下降也在这个范围。对于绝大多数业务系统来说,这个损耗完全可以用监控价值换回来。但如果你做的是极致的低延迟系统,那可以把采样率调低,默认是-1(全量采样),可以改成20表示20%采样,大幅降低上报压力。

7.3 我的三条避坑心得

第一,先小范围试点再全量接入。不要一上来就把所有服务都挂上Agent,问题排查起来容易糊。先挑一个非核心服务跑一天,确认数据完整、性能影响可控,再逐步推广。

第二,让Agent版本和OAP版本保持一致,这个我再强调一遍。很多诡异问题都是因为版本不一致造成的,升级Agent前一定要看官方升级文档,里面有兼容性说明。

第三,日志的TraceID要放进Skywalking。日志关联查询是链路跟踪的隐藏价值,但默认情况下业务日志里没有TraceID,需要你在logback/log4j2里配置%X{tid}之类的转换符。具体做法可以参考Skywalking官方文档里的“Logback TraceId Pattern”,这样排障时就能从日志直接跳到链路,也能从链路反查日志。

8. 后续可以做的一些扩展

链路跟踪只是可观测性的一个维度。集成Skywalking跑通之后,如果时间和精力允许,我建议接着做这几件事:

一是告警配置。Skywalking内置了不少告警规则,比如“接口成功率低于某个阈值”“响应时间超过某个值”等。你可以在config/alarm-settings.yml里调整规则,并配置Webhook,把告警推到钉钉或企业微信。有了告警,监控才真正闭环。

二是对接日志系统。把Skywalking和ELK或Loki联动,让TraceID贯穿日志、指标、链路三个体系,排查效率会再上一个台阶。

三是从单体Demo过渡到微服务场景。动手搭一个SpringCloud全家桶的示例,让多个服务互相调用,再让Gateway统一入口,你会看到拓扑图自动把整个调用网络画出来,那一刻对“链路跟踪能做什么”的感受会更深。

9. 写在最后:另一个小技巧

最后再分享一个实用小技巧:如果你不想在启动命令里写很长一串-D参数,可以直接修改/opt/skywalking-agent/config/agent.config里的agent.service_namecollector.backend_service两个配置项。这样多个项目复用同一份Agent包时,只需要在各自的启动脚本里用-D覆盖不同的服务名即可。我在本地开发的时候,就是在IDEA的.run配置里维护不同项目的VM options,不需要动Agent文件,怎么折腾都不会把公共配置改坏。

链路跟踪这个东西,接入本身不难,难的是持续用它在日常排障中发挥作用。希望这篇教程能帮你从“能跑起来”走到“会用它定位问题”。实操中如果你遇到别的问题,欢迎在评论区留言,我看到了会尽量回复。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询