微服务拆得越多,链路跟踪就越不是可选项,而是刚需。每次线上出问题,要回答的都是同一个问题:一个请求进来,到底在哪个环节慢了、失败了、依赖了谁。这个系列前面写了不少SpringBoot落地方案,这一篇就专门把链路跟踪这块补齐,讲透如何用Skywalking快速接入SpringBoot应用,而且是零侵入、不改业务代码的那种。
先说结论:Skywalking + SpringBoot是当前Java微服务生态里最省事的可观测性组合之一。Java Agent机制让你几乎不需要改动任何源码,就能拿到请求链路、服务拓扑、JVM指标、SQL耗时这类信息。这篇文章适合所有已经在用SpringBoot做微服务、但还没有一套完整链路追踪方案的人。下面从选型、架构原理到实操部署、生产配置、异步线程追踪、告警和坑位排查,完整过一遍。
1. 为什么需要链路跟踪,以及为什么选Skywalking
1.1 一次请求背后到底经历了什么
我先还原一个很常见的场景。用户在前端点了个“提交订单”,后端这边可能并不是一个服务在处理,而是网关、订单服务、库存服务、支付服务、优惠券服务一条链跑下来,中间还有Redis、MQ、MySQL。任何一个环节出问题,表象都是“接口超时”或者“偶发性失败”。
没有链路跟踪的时候排查流程非常痛苦:先打开各个服务的日志,用时间戳人工对齐,猜是哪个服务慢了,然后翻到对应的SQL和第三方调用,再结合线程日志一步步还原。运气好几分钟,运气差一上午都定位不到根因。尤其是那种“请求在中间某个环节被丢弃了”、“慢SQL只在高峰期出现”的问题,日志里根本看不出完整因果,因为一个请求产生的日志散落在多个服务的文件里,连一个统一的traceId都没有。
链路跟踪解决的就是这个核心问题:把一次请求经过的所有服务节点、方法调用、SQL执行、消息发送串成一条完整的时间线,每个环节的耗时、状态、依赖关系一眼可见。
1.2 主流的APM方案怎么选
开源链路跟踪/APM工具不少,很多人项目里可能已经在用Zipkin或者Pinpoint。我简单列一个对比,方便还没有选定方案的人参考。
| 方案 | 接入方式 | 存储依赖 | 观测能力 | 运维成本 | 性能损耗 |
|---|---|---|---|---|---|
| Zipkin | 需要基于Brave或SDK埋点,代码侵入相对高 | ES/Cassandra等 | 偏链路追踪,拓扑和指标能力较弱 | 中 | 中 |
| Pinpoint | Java Agent字节码增强 | HBase | 调用链、拓扑都比较强 | 高,依赖HBase | 偏高 |
| CAT | 需要代码埋点 | 自研存储/HDFS | 偏监控报警 | 高 | 中 |
| Skywalking | Java Agent字节码增强 | H2/ES/MySQL等 | Tracing + Metrics + 拓扑 + 告警,能力全面 | 低 | 低 |
单从SpringBoot项目组的视角,我在实际选择时最看重的是两点:接入成本和生产排错效率。Skywalking在这两方面优势非常明显。Java服务通常跑在Tomcat、SpringMVC这类成熟容器上,Skywalking的插件机制对这些底层组件做了大量适配,所以应用代码基本不需要动,只需要在启动命令里加一个Agent参数,服务拓扑、Span数据、服务指标就自动出来了。Zipkin虽然轻巧,但大多数使用场景需要你在代码里引入依赖、加拦截器或者手写拦截逻辑,这对一个业务代码已经堆得很高的老项目来说,改造风险不低。
还有一点是Skywalking自身不只是一个链路追踪系统,它同时采集Metrics。你在UI里看一个服务,不仅能看调用链,还能看它的响应时间曲线、吞吐量、Apdex分数、JVM堆内存、GC次数和耗时。这意味着一个平台同时覆盖了Tracing和部分基础设施监控的职责,中小团队不用额外再搭一套监控栈。
1.3 Skywalking集成SpringBoot的典型场景
Skywalking接入SpringBoot后实际能自动化拿到什么?以我自己的项目经验为例,SpringBoot应用启动时挂上Agent后,下面这些内容几乎都是零配置直接可用的:
- HTTP入口链路:SpringMVC的Controller会被拦截,形成服务端的Entry Span,路径带上HTTP方法;
- 服务之间的调用:通过RestTemplate、OpenFeign、OkHttp、Apache HttpClient等组件发起的下游HTTP调用会被自动识别为Exit Span;
- 数据库访问:JDBC、MyBatis、Spring JDBC Template执行的SQL会记录在Span里,能看到具体SQL和参数;
- Redis、RabbitMQ、Kafka等中间件访问:自动生成对应的Span节点;
- 线程池和异步调用:配合特定插件或工具包,能跨线程传递上下文;
- JVM基础指标:堆内存、GC次数、CPU等上报到OAP。
所以Skywalking非常适合那种“服务多、组件杂、历史代码不敢乱动”的SpringBoot微服务项目。这一篇里我用的SpringBoot版本是2.7.x和3.x都测过,后面集成步骤不区分版本,只要JDK版本在8到17或者更高,Agent都能正常加载。
2. 了解关键组件和链路模型,后面配环境才不会瞎
2.1 Skywalking的几个核心成员
正式开始部署之前,先花两分钟弄明白Skywalking整体分成哪几块,否则后面看日志会一头雾水。
- Agent:挂在业务应用进程里的Java探针,负责采集链路和指标数据。它本身不存储任何数据,只负责采集和上报。
- OAP Server:Skywalking的Observability Analysis Platform,负责接收Agent上报数据,做聚合分析,并把结果写入存储。可以简单理解为整个系统的计算大脑。
- Storage:OAP的存储层。支持H2、Elasticsearch、OpenSearch等。H2通常用于演示环境,生产环境基本都切ES或者OpenSearch。
- Web UI:Skywalking的控制台界面,负责展示链路、拓扑、指标、告警等信息,本身不直接和Agent通信,而是通过HTTP接口读取OAP的聚合结果。
数据上报链路是:Agent通过gRPC协议上报到OAP,OAP分析完写存储,UI再从OAP拉数据展示。
2.2 Trace、Segment、Span到底是什么关系
在Skywalking的UI里看到的都是一条一条的Trace,但它的底层模型其实分了三层。我用人话解释一下。
一个完整的请求链路叫做Trace。比如用户在浏览器点了一个“查询订单详情”,请求先打到Gateway,再转发到Order Service,Order Service又查了MySQL,还调用了Member Service。这个过程产生的整条链路就是一个Trace。
Trace会被拆分成若干Segment,每个Segment代表一次“进程内的完整处理”。比如Gateway接收到请求并转发这一段是一个Segment,Order Service从收到请求到返回这一段是另一个Segment。也就是说,一个服务进程处理一次请求的所有Span合起来就是一个Segment。
Span是最小单元,表示一个具体操作。比如执行一条SQL、发一次HTTP请求、方法里的一段业务逻辑,都可以对应一个Span。Skywalking把Span分成三种:
- Entry Span:服务入口操作,比如Controller接收到的HTTP请求;
- Local Span:进程内部方法级别的操作,需要手动或通过插件标记;
- Exit Span:服务出口操作,比如调用下游HTTP、执行JDBC、发MQ消息。
你在UI里看到的调用链瀑布图,其实就是一个Trace下面多个Segment、每个Segment下多个Span按时间轴展开的可视化结果。理解了这个模型后,你用UI过滤数据时会顺手很多。
2.3 Java Agent无侵入的原理简单说几句
Skywalking Agent能做到不改代码就采集链路,底层依赖的是Java Agent和字节码增强机制。
Java启动时会先执行premain方法,这是通过-javaagent参数指定的JAR包里的一个特殊入口。Skywalking的Agent在premain阶段拿到JVM的Instrumentation句柄,然后用ByteBuddy等字节码操作库,在类的字节码加载时动态织入增强逻辑。怎么理解这个事情呢?你可以把JVM类加载想象成一条流水线,Skywalking在流水线上装了几个“摄像头”,不改变产品原来的工序,只是在产品经过时自动记录时间和使用情况。
具体到跨服务调用,Skywalking会在HTTP客户端发起请求前,把当前链路的TraceId、SegmentId、SpanId等信息加密后写入请求头中的sw8Header;下游服务收到请求后,负责接收的插件会从Header里解析出这些上下文,并把它作为当前服务新Segment的父上下文。这样即便请求跨了三个服务,链路也能像接力棒一样一环扣一环传递下去。
不过要提醒一句,无侵入并不等于完全什么都不用管。Skywalking的拦截逻辑依赖插件对具体框架的适配。如果你的项目里用了很冷门的RPC框架或自研网络框架,那么链路上下文可能无法自动透传,这种情况就需要用工具包做手动埋点或手动传输上下文。后面第5章会专门说这部分。
3. SpringBoot集成Skywalking完整实操步骤
3.1 用Docker Compose把服务端跑起来
Skywalking的服务端部署通常指OAP和Web UI两个组件。存储可以先用默认H2,等接入生产环境再切ES。我这里给出一个最简的Docker Compose配置,适合本地开发和功能验证。
version: '3.8' services: skywalking-oap: image: apache/skywalking-oap-server:9.7.0 container_name: skywalking-oap restart: always ports: - "11800:11800" - "12800:12800" environment: SW_STORAGE: h2 networks: - skywalking healthcheck: test: ["CMD", "/bin/sh", "-c", "exit 0"] interval: 15s timeout: 5s retries: 3 skywalking-ui: image: apache/skywalking-ui:9.7.0 container_name: skywalking-ui restart: always depends_on: - skywalking-oap ports: - "8080:8080" environment: SW_OAP_ADDRESS: http://skywalking-oap:12800 networks: - skywalking networks: skywalking: driver: bridge直接执行docker compose up -d启动,过十几秒后访问http://localhost:8080,能看到Skywalking的UI界面就算搭建成功。
这里解释几个关键点。
- 11800是gRPC端口,Agent数据上报走这个;12800是HTTP端口,Web UI读取数据和OAP之间通信用的。本地调试时这两个端口都要映射到宿主机。
- 容器内部是通过服务名
skywalking-oap互相访问的,所以UI的SW_OAP_ADDRESS写的是http://skywalking-oap:12800,不能写localhost。 - 示例里存储用的是
SW_STORAGE=h2,官方把H2作为默认零配置存储,但这是内存文件数据库,重启容器后历史数据会丢,只适合体验功能。
如果你的宿主机8080被其他服务占用,把UI那段端口映射改成8081:8080即可。
3.2 下载Skywalking Agent并配置到SpringBoot应用
服务端就绪后,接着要准备Agent,也就是要挂到SpringBoot应用进程里的探针包。
你需要到Skywalking官网下载和OAP版本对应的二进制发布包。比如这里OAP用的是9.7.0,Agent也要用同一个版本,版本不一致可能导致数据上报失败或解析异常。下载解压后,目录里的agent/skywalking-agent.jar就是核心探针。把这个路径记好,下面会用到。
本地开发调试时,最简单的方式是在IDE的VM options里加上探针参数。以IDEA为例,在SpringBoot启动类的Run Configuration里,把下面这段加到VM options中。
-javaagent:/你的路径/skywalking-agent.jar -Dskywalking.agent.service_name=order-service -Dskywalking.collector.backend_service=127.0.0.1:11800然后再正常启动SpringBoot应用。启动日志里如果看到类似Skywalking Agent相关的输出,说明探针已经加载成功。
Linux服务器上直接java -jar启动也一样,无非是把参数放到命令行上。
java \ -javaagent:/opt/skywalking-agent/skywalking-agent.jar \ -Dskywalking.agent.service_name=order-service \ -Dskywalking.collector.backend_service=192.168.1.100:11800 \ -jar order-service.jar三个参数的含义分别是:
-javaagent:指定探针JAR包路径,注意是Agent的JAR,不是skywalking整个安装包。-Dskywalking.agent.service_name:当前服务在Skywalking UI里显示的名字。建议和SpringBoot应用的spring.application.name保持一致,不然后面看拓扑图会一脸懵。-Dskywalking.collector.backend_service:OAP Server的地址和gRPC端口。
我是强烈建议把这三个参数配置成环境变量或配置文件维护。因为服务一旦多了,每个服务的service_name和IP都不同,写死在启动命令里容易漏改。
3.3 容器环境下SpringBoot挂Agent的两种姿势
SpringBoot项目现在生产环境大多容器化部署了。Docker部署时有两种方式挂探针。
第一种方式是把Agent目录挂载进容器,并通过JAVA_TOOL_OPTIONS环境变量传给JVM。Dockerfile里并不需要你做任何额外操作,运行容器的时候指定环境变量即可。
docker run -d \ --name order-service \ -p 8081:8080 \ -e JAVA_TOOL_OPTIONS="-javaagent:/agent/skywalking-agent.jar -Dskywalking.agent.service_name=order-service -Dskywalking.collector.backend_service=192.168.1.100:11800" \ -v /opt/skywalking-agent:/agent \ your-registry/order-service:1.0.0第二种方式是把Agent直接打进镜像。适合团队统一管理探针版本的场景,比如整个部门都规定使用9.7.0版本。
FROM openjdk:17-jdk-slim WORKDIR /app COPY --from=apache/skywalking-java-agent:9.7.0 /skywalking/agent /opt/skywalking-agent COPY order-service.jar /app/order-service.jar ENV JAVA_TOOL_OPTIONS="-javaagent:/opt/skywalking-agent/skywalking-agent.jar" ENV SW_AGENT_NAME=order-service ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.1.100:11800 EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/order-service.jar"]这里有个很多新手没注意到的点:JAVA_TOOL_OPTIONS环境变量会被JVM自动识别,不需要改启动命令。但注意,如果镜像里原本已经设置了JAVA_TOOL_OPTIONS,追加时要保留原值,不要直接覆盖。
3.4 如何确认SpringBoot已经成功接入
接入后最重要的事情就是验证链路数据有没有真正上报到Skywalking。
先在本地启动两个SpringBoot服务,一个假设叫gateway-service,一个叫order-service。再让order-service提供一个HTTP接口,内部用RestTemplate调用gateway-service的接口,模拟简单的服务间调用。
然后分别用上一节的VM options启动这两个服务,打开浏览器访问order-service的接口几次。过大约30秒后,打开Skywalking UI,在左侧菜单的“General Service”列表里,应该能看到gateway-service和order-service这两个服务名。点进Service Topology页面,能看到两个服务节点之间有连线,线上面会有吞吐量和时延数据。
如果Topology页面里两个服务没有连上线,先不用急着排查,把调用接口的流量再多打几次。Skywalking的数据采集和聚合是异步的,UI上的展示存在一定延迟,一般几十秒内会刷新出来。
看不到服务时的排查顺序一般是这样:先看SpringBoot日志有没有Agent加载成功,再检查OAP的11800端口是否通,最后确认-Dskywalking.collector.backend_service地址填的是不是宿主机IP而不是localhost。容器里的OAP如果映射了端口,宿主机上访问没问题,但如果SpringBoot也跑在另一个容器里,localhost指向的是容器自己,自然连不上OAP。
4. 从演示环境走向生产:存储、采样和告警配置
4.1 存储从H2切到Elasticsearch
用H2做存储存在两个比较大的问题:一是H2重启会丢历史链路数据;二是H2对大量链路数据查询和聚合的性能不够,尤其是服务数量和调用量上来后,UI打开都很卡。
生产环境建议把OAP的存储切到Elasticsearch。Skywalking官方对ES的兼容性做得比较好,9.x版本推荐使用ES 7.x。切存储很简单,修改Docker Compose里OAP的环境变量即可。
environment: SW_STORAGE: elasticsearch SW_STORAGE_ES_CLUSTER_NODES: 192.168.1.200:9200 SW_STORAGE_ES_INDEX_SHARDS: "3" SW_STORAGE_ES_INDEX_REPLICAS: "1"如果ES启用了用户名密码认证,还要额外加上SW_ES_USERNAME和SW_ES_PASSWORD环境变量。
切ES之后,OAP启动时会自动创建Skywalking所需的索引模板和索引,不需要手动在ES里建表。这里提醒一下,OAP容器首次启动时要保证能连通ES,如果网络不通,OAP会启动失败或一直处于重试状态。建议先单独启动ES,确认可用后再启动OAP。
切到ES以后,UI上虽然看不到明显变化,但重启OAP容器就不会丢数据了。链路数据的保留时间由索引生命周期策略决定,你需要根据磁盘容量和业务需要设置ES索引的保留天数。Skywalking的很多索引是按日期滚动创建的,比如segment_snapshot_2025-01-01这种模式,到点删除过期索引即可。
4.2 Agent的采样率要主动调,别傻傻全量上报
很多人第一次接入后会发现一个现象:明明压测打了大量请求,UI上却只显示少量链路,这不是丢数据,而是Skywalking默认的采样策略导致的。
Skywalking Agent默认的采样方式是“每3秒最多采样N条请求”,默认N是3。这是Agent为了降低自身性能开销而设计的机制。对于排障来说,全量采样确实能保留更多现场,但代价是网络、磁盘和OAP的计算开销都会上升。
你可以通过-Dskywalking.agent.sample_n_per_3_secs参数调整采样率,把它设置成-1表示关闭采样,全量上报。有几种情况可以考虑适当调这个值:
- 测试环境或本地联调,需要反复验证链路是否准确,直接全量采样,调成-1最省心;
- 生产核心交易链路,最好全量采样或调整到较高的值,否则你要排查问题的时候,恰好那笔异常请求没被采样,就只能干瞪眼;
- 非核心服务或日志量特别大的服务,可以适当把采样参数调大,但要结合OAP压力和ES存储容量。
另外,如果你需要针对特定请求强制上报,不受采样限制,Skywalking也提供了基于配置的强制采样规则,可以通过agent.sampling相关配置对指定路径做全量采集。这个功能适合只对某些核心接口做全采,而其他接口保持抽样。
4.3 告警规则配置与Webhook推送
链路跟踪最直接的价值之一是能第一时间发现服务状态恶化。Skywalking内置了一组告警规则,比如服务响应时间超过阈值、服务成功率下降、JVM GC时间过长等。
默认告警规则文件在OAP容器的/skywalking/config/alarm-settings.yml里。如果你想修改默认配置,有几种做法:
- 在Docker部署时通过挂载文件的方式替换容器内的
alarm-settings.yml; - 进入OAP容器直接编辑文件后重启;
- 直接使用Skywalking 9.x中UI上提供的告警配置管理功能。
生产需要对接钉钉、企业微信或自研IM系统时,Skywalking配置的Webhook地址就是一个HTTP接口。如果你用的IM平台提供的是Webhook机器人,直接把机器人地址填到Skywalking的webhooks列表里即可。告警触发时Skywalking会推一个告警列表JSON过去。有些平台的机器人签名算法比较特殊,你可能需要在OAP和IM之间额外加一个轻量的转发服务,把Skywalking的告警JSON转成IM平台需要的消息格式。
我的实践建议是:告警规则不要一上来就配得很敏感,否则半夜会被无关告警轰炸。先把“服务平均响应时间超过2秒持续3分钟”、“服务错误率超过10%持续2分钟”这类明显异常规则配上,跑一段时间后逐步调阈值到适合自己业务的档位。
5. 让链路数据更精细:异步、自定义埋点和慢SQL分析
5.1 异步线程处理出现链路断点怎么办
SpringBoot项目一旦用了@Async或者自己创建线程池去执行任务,就会出现一个典型问题:异步线程里打印的日志、执行的SQL虽然是链路的一部分,但Skywalking默认无法把它们关联到发起请求的主链路上。原因很简单,链路上下文是保存在线程本地变量里的,跨线程传播需要额外的处理。
Skywalking提供的apm-toolkit-trace工具包里有现成的RunnableWrapper和CallableWrapper可以用。最简单的使用方式是在往ThreadPoolExecutor或new Thread里提交任务时包装一层。
import org.apache.skywalking.apm.toolkit.trace.RunnableWrapper; executor.submit(RunnableWrapper.of(new Runnable() { @Override public void run() { // 异步执行的业务逻辑 doSomething(); } }));有的项目已经用了Spring的@Async,这时候可以自定义一个AsyncConfigurer,在里面把我们注入的线程池替换成包装过的TaskDecorator。
@Configuration public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(8); executor.setMaxPoolSize(16); executor.setQueueCapacity(100); executor.setThreadNamePrefix("async-"); // 通过TaskDecorator把主线程的Trace上下文透传到异步线程 executor.setTaskDecorator(new SkywalkingContextDecorator()); executor.initialize(); return executor; } }这里SkywalkingContextDecorator的核心逻辑就是用Skywalking Toolkit里提供的TraceContext.capture和TraceContext.continue来传递上下文。如果你的项目用的是Skywalking 8.9以上版本,老版本的API可能已经标记废弃,建议优先使用RunnableWrapper或CallableWrapper方案,代码改动量最小。
5.2 自定义Span标记关键业务操作
大部分链路数据靠插件自动采集就够了,但有一种场景必须手动埋点:某个关键业务流程特别耗时,你需要精确看到这个方法自身的耗时,以及它内部执行完HTTP、SQL后剩余的时间花在哪。这时可以给方法加上@Trace注解。
import org.apache.skywalking.apm.toolkit.trace.Trace; @Service public class OrderService { @Trace(operationName = "calculateDiscount") public BigDecimal calculateDiscount(Long userId, Long orderId) { // 复杂规则计算 } }加了这个注解后,这个方法就会在链路里变成一个本地Span,你能在UI上直接看到它的耗时。如果需要在Span里附带上下文信息,比如用户ID、订单号,可以再配合ActiveSpan操作。
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan; import org.apache.skywalking.apm.toolkit.trace.Trace; @Trace(operationName = "createOrder") public Long createOrder(OrderCreateRequest request) { ActiveSpan.tag("userId", request.getUserId()); ActiveSpan.tag("orderType", request.getOrderType()); // ... }这些tag在排查“某个异常订单是不是同一个用户类型”这类问题时很有用,相当于在链路里给特定环节做了标记。但是要注意别把手机号、身份证号等敏感信息放进去,Skywalking的日志和UI默认是明文展示的,涉及用户隐私的数据要格外谨慎。
5.3 慢SQL分析用链路来定位
Skywalking对JDBC插件有很好的支持,SpringBoot项目只要用的是标准JDBC连接池(比如HikariCP、Druid、Tomcat JDBC)+ 常见驱动,SQL执行链路会自动被采集。
在数据库访问异常的场景里,我最常做的是点开一个慢Trace,在Span详情里找到Database类型的节点,名称往往会带上连接的主机和端口。点开详情后你能看到执行的SQL语句、执行时间、受影响行数。结合调用方链路上下文,很快就能判断慢SQL是不是导致整个接口超时的主要原因。
对一些低版本的MySQL驱动或者自定义连接器,如果发现Trace里没有SQL详情,可以先查一下agent/plugins目录下有没有对应的JDBC插件。Skywalking的插件目录在agent包解压后的plugins和optional-plugins里,optional-plugins目录下的插件默认不激活,需要的情况要把JAR包手动复制到plugins目录再重启应用。
6. 踩坑日志:常见问题与个人项目里的排查习惯
6.1 服务列表为空或Agent加载失败
最常遇到的问题就是Skywalking UI里看不到任何服务。我遇到过的原因基本集中在这几个方面:
- Java启动参数没有生效,
-javaagent写错路径或者IDE里没配置到VM options里; - Agent加载成功后日志里出现了某些类的增强失败,但因为是WARN级别,不仔细看日志很难发现;
- OAP的11800端口没开放,云服务器安全组或本地防火墙把gRPC端口挡了;
- Agent版本和OAP版本不一致,Agent上报的数据OAP无法正常解析,UI里服务列表就始终为空。
排查时可以先用一条最简单的测试链路验证:一个SpringBoot服务,一个接口,一个Agent。如果它能正常出现在UI里,再逐渐增加服务和调用链长度。这样能帮你快速定位是全局配置问题,还是某个服务的配置问题。
6.2 Swagger和内部接口的大量无效Trace
SpringBoot项目基本都会集成Swagger,有些还会做健康检查、内部调度任务。如果不对这些接口做排除,Skywalking会把健康检查、Swagger的访问也当成有效请求采集进来,导致服务列表里的“请求量”看起来虚高,告警也会被这些无效流量干扰。
可以通过Skywalking的-Dskywalking.trace.ignore_path参数来排除不需要追踪的路径。这是一个正则表达式配置,多个路径用逗号分隔。
-javaagent:/你的路径/skywalking-agent.jar -Dskywalking.agent.service_name=order-service -Dskywalking.collector.backend_service=127.0.0.1:11800 -Dskywalking.trace.ignore_path=/actuator/.*,/v2/api-docs,.*/swagger.*实际配置时建议从最简单的排除开始,比如先只排除/actuator/health,确认有效后再把Swagger相关的路径也加上。别一上来就复制一大串正则,出问题以后反而不好定位是哪个规则写错了。
6.3 探针本身对业务带来的性能影响
Skywalking的性能开销在同类工具里控制得算好,官方做过压测,引入Agent后吞吐损失通常在个位数百分比。但是生产环境如果对性能特别敏感,建议做一次压测对比,再决定是否全量采样。
如果发现性能下降超出预期,优先检查是不是openjdk版本和skywalking-agent对某些字节码操作不兼容导致的异常回退。这时候可以在启动参数里临时加-Dskywalking.agent.force_tls=false这样无关痛痒的参数吗,还真不是,这种问题一般需要从Agent日志里找线索。
我个人见过的一个真实案例是:某些高并发场景下,Agent的采样默认值每3秒3条会严重“掩盖”瞬时尖峰。因为数据被采样后,链路看起来一切正常,但实际系统已经出现毛刺了。解决思路是调高采样率或全量采样,再结合Skywalking的Metrics曲线去判断是不是还有性能抖动。
6.4 日志和链路TraceId打通的问题
链路数据在Skywalking UI里看是一条完整的Trace,但排查问题经常还要回到具体应用的日志里看业务异常栈。如果日志和TraceId不打通,你从UI定位到一个异常Span后,还要跑到日志系统里去按时间和关键字重新搜一遍,效率低很多。
Skywalking推荐的方案是Java Agent里提供一个日志增强组件,把链路上下文里的traceId自动注入到日志的MDC里。你只要在logback的pattern里增加%X{tid}这个占位符,日志里就会出现对应的traceId。这样从Skywalking UI拿到traceId后,直接在日志平台搜索就能定位到同一链路的具体业务日志。
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{tid}] - %msg%n</pattern>如果没有采用这个方案,也可以自己写一个拦截器,在请求进Controller前从Skywalking上下文中获取traceId并塞到MDC里。但自己实现时要注意异步线程的MDC也需要处理,不然异步日志又会丢掉traceId。既然Agent已经支持日志增强,我更推荐直接用官方方案,省得自己维护一套MDC透传逻辑。
6.5 我个人的实施习惯和收尾建议
把Skywalking从零搭起来并不难,真正难的是让它持续稳定地为团队提供价值。我在项目里落地时有几个习惯,最后一起分享出来。
第一个习惯是把Skywalking的部署和配置做成项目内的Docker Compose或K8s YAML模板,不管新服务还是老服务,接入时只要按模板填服务名和OAP地址就行,不要在每次接入时重新发明一套参数。第二个习惯是Agent版本、OAP版本、UI版本三者的版本必须保持一致,我吃过版本不一致导致数据上报后UI查不到的亏,从那以后升级时都是三件套一起升。第三个习惯是告诫团队不要刻意依赖Skywalking全量链路来替代必要的业务日志规范,链路侧重依赖关系和执行耗时,业务侧的状态流转、参数快照、异常原因还是要保留在日志里,两者配合才能最快定位生产问题。
链路跟踪这个东西,做完接入只是开始。等数据和告警真正在你系统的日常运维里发挥作用之后,你会发现它带来的收益远不止“少熬夜排查问题”这么简单。希望这一篇能帮你把SpringBoot项目和Skywalking之间的路彻底趟平,少走一些我走过的弯路。