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 我的选型理由
我自己在中小型微服务团队里做技术选型时,核心考量有四个:
- 接入成本:Zipkin和Jaeger在SpringBoot里接入需要主动引入依赖、配置
spring.sleuth相关参数,并且很多场景还要手动写Span。Skywalking只需要改启动脚本,业务代码零改动,对一个存量项目来说,这个优势是压倒性的。 - 功能完整度:Skywalking不只是链路跟踪,它还能展示服务之间的依赖拓扑、每个接口的响应时间分布、JVM内存/GC情况,甚至SQL执行参数。一套系统解决监控和追踪两个问题,省事。
- 中文社区和文档:这个不用多说,Skywalking的中文资料、博客、社区讨论非常丰富,遇到问题搜索一下基本都有答案。工具再强,没人答疑也费劲。
- 部署和维护成本: 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 端口说明与网络规划
很多同学第一次部署容易混淆端口角色,我列个表方便以后查:
| 端口 | 所属进程 | 用途 | 注意事项 |
|---|---|---|---|
| 11800 | OAP | Agent上报链路数据的gRPC端口 | Agent配置里填这个端口 |
| 12800 | OAP | UI查询数据用的HTTP端口 | 一般调试接口时才用 |
| 8080 | UI | Skywalking前端页面 | 可映射成宿主机其他端口 |
如果公司网络策略严格,需要保证应用服务器能访问OAP的11800端口,UI那台机器能访问12800和8080端口。我遇到过明明UI能看到数据,但Agent一直不上报的情况,排查到最后发现是11800端口在安全组里没放通。
4. SpringBoot项目接入:两分钟完成Agent配置
服务端就绪后,接下来把SpringBoot项目接入Skywalking。这是整篇教程里最爽的一步,因为不需要pom里加任何依赖,不需要写任何配置类,只需要改启动命令。
4.1 下载Agent包
首先到Skywalking官网下载对应版本的Agent发行包,通常在agent目录下会有这些内容:
skywalking-agent.jar:Agent的核心入口JARconfig/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 异步线程场景的处理
如果业务代码里用了ExecutorService、CompletableFuture,或者用了@Async注解,子线程里的调用默认不会被串到父链路里。解决办法是使用Skywalking提供的包装类:
ExecutorService executorService = Executors.newFixedThreadPool(10); executorService.execute(CallableWrapper.of(() -> { // 这里会继承主线程的Trace上下文 remoteService.call(); }));对应还有RunnableWrapper、SupplierWrapper,包在任务外层即可。原理是在任务执行前临时把父线程的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-service和order-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_name和collector.backend_service两个配置项。这样多个项目复用同一份Agent包时,只需要在各自的启动脚本里用-D覆盖不同的服务名即可。我在本地开发的时候,就是在IDEA的.run配置里维护不同项目的VM options,不需要动Agent文件,怎么折腾都不会把公共配置改坏。
链路跟踪这个东西,接入本身不难,难的是持续用它在日常排障中发挥作用。希望这篇教程能帮你从“能跑起来”走到“会用它定位问题”。实操中如果你遇到别的问题,欢迎在评论区留言,我看到了会尽量回复。