日志和指标回答不了“慢在哪一跳”
Prometheus 能告诉你订单接口 P99 是 800ms,日志能告诉你某个请求最终失败了,但两者都很难回答一个具体问题:这 800ms 里,网关、订单服务、库存服务、MySQL 各占多少?链路追踪补的就是这一块——它把一次请求经过的所有服务、方法、SQL 串成一棵树,每一段都有耗时和状态。
SkyWalking 是国内落地最多的 APM 之一,Java agent 无侵入接入,OAP 负责聚合,UI 负责查询。下面按“挂 agent → 串上下文 → 关联日志”的顺序讲,最后说常见的断链原因。
接入:agent 怎么挂
SkyWalking 10.x 的 OAP 默认监听 11800(gRPC)和 12800(HTTP),UI 在 8080。Java 应用只需要挂 agent,不改一行业务代码:
java -javaagent:/opt/skywalking/agent/skywalking-agent.jar \
-Dskywalking.agent.service_name=order-service \
-Dskywalking.collector.backend_service=oap:11800 \
-jar order-service.jar
容器里更常见的做法是挂载 agent 目录,用环境变量注入:
env:
- name: SW_AGENT_NAME
value: order-service
- name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
value: oap:11800
- name: JAVA_TOOL_OPTIONS
value: -javaagent:/skywalking/agent/skywalking-agent.jar
-javaagent 必须放在 -jar 之前,放在后面会被当成传给应用的参数,agent 根本不会加载。service_name 建议带上环境后缀或用 namespace 隔离,否则测试环境的慢请求会混进生产拓扑。
agent 自带常用插件,网关、WebFlux、RocketMQ、Kafka、JDBC 等部分插件在 optional-plugins 目录下,需要按实际版本复制到 plugins 才会生效。升级 agent 时,插件和 toolkit 的版本要一起升。
上下文传播:HTTP 只是第一步
跨进程的 traceId 通过请求头传递,SkyWalking 用的是 sw8(以及用于自定义透传的 sw8-correlation)。Feign、RestTemplate、Gateway、Dubbo 这些都有现成插件,正常情况下不需要手工处理。
真正容易断的是异步。下面这些场景默认会丢上下文:
- 自定义
ThreadPoolExecutor里提交任务; CompletableFuture.supplyAsync使用默认线程池;@Async方法;- 定时任务里发起的调用;
- 自己 new 出来的线程。
解决办法分两种:把 agent 的 apm-jdk-threadpool-plugin、ForkJoinPool 相关插件复制到 plugins,让字节码增强自动包装 Runnable/Callable;或者在代码里用 @TraceCrossThread 包装任务。用 @Async 时还要注意,Spring 的线程池如果被自定义 TaskDecorator 包裹,装饰器里不要再做会破坏包装顺序的事情。
消息队列的上下文由 producer/consumer 插件负责,但前提是生产端和消费端都挂了 agent。只挂一半,消费端就会开启一条新链路,看起来像“消息凭空产生”。
把 traceId 打进日志
链路追踪和日志必须能互相跳转,否则查到一条慢 trace 却找不到对应日志。引入 toolkit 后在 logback 里注册 %tid:
<conversionRule conversionWord="tid"
converterClass="org.apache.skywalking.apm.toolkit.log.logback.v1.x.LogbackPatternConverter"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%tid] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
依赖是 apm-toolkit-logback-1.x,版本和 agent 保持一致。Spring Boot 3.3 用的是 Logback 1.5,升级前先在测试环境确认 toolkit 兼容,否则可能出现 %tid 输出为空或者启动报转换器找不到。
打印出 traceId 后,日志平台按 traceId 建索引,链路和日志就能双向跳转。注意 traceId 只适合放在日志正文里,不要做成 Loki 的标签,否则标签基数会爆炸。
常见坑
坑一:只看到自己的服务,拓扑是断的。 先确认下游有没有挂 agent,再看请求头 sw8 有没有被网关或代理删掉。有些网关会过滤未知请求头,跨域配置也可能影响自定义头。
坑二:线程池里没有 traceId。 典型表现是日志里 %tid 为空,或者 span 的父节点对不上。先补线程池插件,再考虑手工包装。
坑三:采样率设得太低。 为了省存储把采样率调到 1%,出问题时刚好没采到。可以只对慢请求和异常请求做全采样,正常请求降采样。
坑四:一个服务名被多个环境共用。 测试、预发、生产的数据混在一起,拓扑和指标全乱。用 namespace 或服务名后缀隔离,不要图省事。
坑五:OAP 存储写满。 UI 突然查不到新数据,应用侧却一切正常。检查 OAP 日志和 Elasticsearch 磁盘水位,设置 TTL 和索引生命周期。
排查步骤
- 看应用启动日志里有没有
SkyWalking agent started,没有就先解决 agent 加载问题。 - 在 UI 里按服务名和时间查 trace,确认请求有没有进来;没有的话查网关是否透传了
sw8。 - 拿到 traceId,去日志平台搜同一条链路,确认日志和 span 能对上。
- 看断点位置:断在 HTTP 调用就查插件和请求头,断在线程池就查异步包装,断在 MQ 就查两端 agent。
- 看 OAP 的
/healthcheck和存储状态,排除采集端问题。
评论