Java 程序员第 46 阶段04:大模型调用链路追踪,SkyWalking 排查线上性能,Spring Boot接入实战
2026/8/13 11:48:55 网站建设 项目流程

  1. 环境准备
  2. 自动埋点接入(Agent 方式)
  3. application.yml 配置
  4. 手动埋点(@Trace / ActiveSpan)
  5. 大模型调用链路追踪实战
  6. 常见问题排查

1. 环境准备

本篇在 Spring Boot 3.x 工程上,演示从零接入 SkyWalking,并针对大模型(OpenAI 风格 / 自研 LLM 网关)调用做链路追踪。

1.1 版本匹配

组件

版本建议

说明

---

---

---

Spring Boot

3.2.x

JDK 17

SkyWalking

9.7+

Agent 与 OAP 同版本

OAP + UI

9.7+

独立部署

Storage

Elasticsearch 8.x

或 BanyanDB

1.2 目录规划

project/
├── docker-compose.yml # OAP + ES + UI
├── agent/
│ └── skywalking-agent.jar # 探针
└── src/main/resources/
└── application.yml

2. 自动埋点接入(Agent 方式)

自动埋点无需改业务代码,仅需在启动时挂载 Agent。SkyWalking 已内置对 Spring MVC、OpenFeign、RestTemplate、JDBC、Redis、Kafka 等组件的插件。

2.1 启动脚本挂载 Agent

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

2.2 通过 IDEA / Maven 配置

在 `pom.xml` 中约定 agent 路径,也可在启动参数里直接写:

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<jvmArguments>
-javaagent:${project.basedir}/agent/skywalking-agent.jar
-Dskywalking.agent.service_name=chat-service
-Dskywalking.collector.backend_service=oap:11800
</jvmArguments>
</configuration>
</plugin>

启动后,访问一次接口,去 SkyWalking UI 的「拓扑」即可看到 `chat-service` 节点与其下游依赖。

3. application.yml 配置

除启动参数外,部分行为建议落到配置文件(或 `agent.config`)中统一管理。

# application.yml(业务侧仅声明自身配置)
spring:
application:
name: chat-service
datasource:
url: jdbc:mysql://mysql:3306/chat
redis:
host: redis
# SkyWalking 推荐通过 agent.config 控制,关键项示例:
# agent.service_name=${SW_AGENT_NAME:chat-service}
# collector.backend_service=${SW_BACKEND:oap:11800}
# agent.sample_rate=10000
# plugin.toolkit.log.grpc.reporter.server_host=oap

> 最佳实践:启动参数用于"环境相关"的地址/服务名,`agent.config` 用于"策略相关"的采样率/忽略后缀,二者配合,避免硬编码。

4. 手动埋点(@Trace / ActiveSpan)

自动埋点覆盖通用框架,但**大模型调用、内部业务片段**需要用手动埋点补充,否则链路会"断"或"粗"。

4.1 @Trace 注解

SkyWalking 提供 `org.apache.skywalking.apm.toolkit.trace.Trace` 注解,标注的方法会被当作一个 Local Span。

import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.apache.skywalking.apm.toolkit.trace.Tag;
@Service
public class ChatService {
@Trace(operationName = "buildPrompt")
@Tag(key = "prompt.length", value = "arg[0].length()")
public String buildPrompt(String userQuery) {
// 组装提示词,作为独立 Local Span 便于定位耗时
return PromptTemplate.render(userQuery);
}
}

4.2 ActiveSpan 手动打 Tag / Log

在方法内部可获取当前 Span,附加自定义标签(如大模型参数、Token 数):

import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
public CompletionResult callLLM(String model, String prompt) {
ActiveSpan.tag("llm.model", model);
ActiveSpan.tag("llm.prompt.length", String.valueOf(prompt.length()));
long start = System.currentTimeMillis();
CompletionResult result = llmClient.complete(model, prompt);
ActiveSpan.tag("llm.token.prompt", String.valueOf(result.promptTokens()));
ActiveSpan.tag("llm.token.completion", String.valueOf(result.completionTokens()));
ActiveSpan.tag("llm.cost.usd", result.costUsd().toString());
ActiveSpan.tag("llm.ttft.ms", String.valueOf(result.timeToFirstTokenMs()));
return result;
}

4.3 跨线程上下文传播

异步调用会丢失上下文,需用 SkyWalking 提供的包装器:

import org.apache.skywalking.apm.toolkit.trace.RunnableWrapper;
CompletableFuture.supplyAsync(RunnableWrapper.of(() -> {
// 此处的 Span 上下文与父线程一致
return callLLM(model, prompt);
}));

5. 大模型调用链路追踪实战

下面用一个完整示例,把「HTTP 调用 LLM 网关」纳入链路,并采集 Token / 时延 Tag。

5.1 自定义 LLM Exit Span 拦截器(插件式增强)

对于 SkyWalking 未内置的 LLM SDK,建议写一个自动插件(参考第 03 篇),在 SDK 的 `complete` 方法上织入 Exit Span:

// 简化:手动方式在业务层包一层 ExitSpan
public CompletionResult tracedComplete(String model, String prompt) {
// 标记为 Exit:跨进程访问远端 LLM
AbstractSpan span = ContextManager.createExitSpan(
"LLM/" + model, new ContextCarrier(), "llm-gateway:443");
try {
ActiveSpan.tag("llm.model", model);
CompletionResult r = llmClient.complete(model, prompt);
ActiveSpan.tag("llm.token.prompt", String.valueOf(r.promptTokens()));
ActiveSpan.tag("llm.ttft.ms", String.valueOf(r.timeToFirstTokenMs()));
return r;
} catch (Exception e) {
ActiveSpan.error(e); // 标记 Span 异常
throw e;
} finally {
ContextManager.stopSpan();
}
}

5.2 Controller 串联整条链路

@RestController
@RequestMapping("/chat")
public class ChatController {
@GetMapping("/ask")
public Map<String, Object> ask(@RequestParam String q) {
// EntrySpan 由 Spring MVC 插件自动创建
String prompt = chatService.buildPrompt(q); // @Trace Local Span
List<Doc> docs = vectorService.search(q); // 自动 Exit Span(MySQL/向量)
CompletionResult r = llmGateway.tracedComplete("gpt-4o", prompt); // 手动 Exit Span
Map<String, Object> resp = new HashMap<>();
resp.put("answer", r.text());
resp.put("tokens", r.promptTokens() + r.completionTokens());
return resp;
}
}

此时一次 `/chat/ask` 调用在 UI 中呈现的 Span 树见图 figure_04_3:Entry → buildPrompt(Local) → vectorSearch(Exit) → LLM/Exit,各段时延与 Tag 一目了然。

6. 常见问题排查

6.1 拓扑看不到服务

现象

可能原因

处理

---

---

---

服务不显示

Agent 未挂载 / backend_service 错误

检查 `-javaagent` 与地址

无 Trace

采样率过低

调高 `agent.sample_rate`

链路断点

跨进程未传播

确认网关/SDK 透传 sw8 头

6.2 链路断在异步/线程池

现象:下游服务的 Span 不在同一 Trace。原因多为线程切换丢失上下文。

// 错误:直接 new Thread,上下文丢失
new Thread(() -> callLLM(...)).start();
// 正确:使用 RunnableWrapper 传播上下文
new Thread(RunnableWrapper.of(() -> callLLM(...))).start();

6.3 大模型链路被采样丢弃

大模型调用慢且贵,必须排除采样。在 `agent.config`:

agent.sample_rate=10000
# 对慢端点强制记录(OAP 侧 slowTraceThreshold 配置)

6.4 Agent 启动报错 ClassCircularityError

多为插件冲突,精简 `plugins/` 目录,只保留实际使用的插件(如只留 `spring-mvc`、`httpclient`、`jdbc`、`redis`)。

小结

本篇落地了 Spring Boot 接入 SkyWalking 的完整路径:Agent 自动埋点零侵入接入通用组件;`@Trace` 与 `ActiveSpan` 补充业务与大模型段埋点;通过 `RunnableWrapper` 解决异步上下文传播;并给出链路断点、采样、插件冲突等常见坑的排查表。下一篇我们将系统规范大模型链路的 Trace/Span 建模与 Tag 命名,让追踪数据真正可分析。

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

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

立即咨询