适用读者:要在现有 Spring Boot 3 项目里把链路跑起来的人;照着旧文加 Sleuth 却启动失败的人。
前置:S16-J01、S07(Spring)、S15(Docker)。
覆盖旧文:A-560 → 67 点(§2 Spring Boot 接入、现代接入、采样率、traceId 入日志、请求传播)。
扩展点:E-S16-006(采样率三策略)。
上一篇:S16-J01 · 链路追踪基础:Trace / Span / Parent Span
下一篇:S16-J03 · SkyWalking。
1. 一句话结论
Boot 3 的追踪栈是"Micrometer Tracing(门面)+ Brave/OTel(实现)+ Zipkin(后端)"三件套;缺任何一件,traceId 都不会出现。 而"没出现"最常见的三个原因是:桥接依赖缺失、采样率过低、日志格式没加占位符。
2. 依赖(Spring Boot 3.3.6 / Java 17,2026-11 实测)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 门面 + Brave 桥接:生成/传播 traceId 的核心 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!-- 把 span 上报到 Zipkin -->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
| 依赖 | 作用 | 缺了会怎样 |
|---|---|---|
micrometer-tracing-bridge-brave |
桥接具体实现 | 无 traceId,日志里是空 |
zipkin-reporter-brave |
HTTP 上报 | 本地有 traceId,Zipkin UI 查不到 |
spring-boot-starter-actuator |
暴露 /actuator 观测端点 |
Observation 不自动装配 |
Sleuth 已不适用于 Boot 3:
spring-cloud-starter-sleuth在 Boot 3 中不再被支持,旧文里的写法会直接失败。正确路径就是上面的 Micrometer Tracing。
3. 配置
# application.yml
spring:
application:
name: order-service
management:
tracing:
sampling:
probability: 0.10 # 10% 采样;开发环境可设 1.0
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
logging:
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
# 起 Zipkin(内存存储,仅用于本地)
docker run -d --name zipkin -p 9411:9411 openzipkin/zipkin:3.4
预期结果:
2026-11-06 15:02:11.882 INFO [order-service,4bf92f3577b34da6,00f067aa0ba902b7] 1 --- [nio-8080-exec-1] c.e.OrderController : 创建订单 sku=1 qty=2
三个关键点:traceId 非空、spanId 非空、两个服务打印的 traceId 相同。
4. 采样率三策略(E-S16-006)
management.tracing.sampling.probability 是头部采样(在请求入口就决定采不采),简单但会漏掉"低概率但高价值"的请求。
| 策略 | 实现 | 优点 | 缺点 |
|---|---|---|---|
| 头部采样 | probability: 0.1 |
零成本、实现简单 | 错误请求大概率被丢 |
| 尾部采样 | 收集后在 Collector 侧按规则过滤(OTel Collector / Zipkin 侧不做) | 错误链路 100% 保留 | 需要 Collector、内存缓冲 |
| 按错误强制采 | 应用侧对 status=ERROR 打 sampled=1 标记 |
关键链路必留 | 需改代码/自定义 sampler |
工程折中(推荐起步):
生产 1–5% 头部采样 + 错误链路强制采样 + 慢请求(> P99)强制采样
开发/预发 100%
判据:抽 10 条线上错误告警,能否在 Zipkin 里找到对应 trace?找不到 → 采样策略不合格。
5. traceId 进日志与跨线程传播
5.1 日志
上面 logging.pattern.level 里的 %X{traceId:-} 依赖 MDC。Boot 3 由 Micrometer Tracing 自动注入,前提是走 Spring 的 Observation/拦截器。
| 场景 | traceId 是否自动带 | 补救 |
|---|---|---|
| Controller(HTTP 入口) | ✅ | — |
@Async / 线程池 |
❌ | TaskDecorator 复制 MDC |
| MQ 消费 | ❌ | 手动从消息头取 traceparent 并 Span.current() 关联 |
| 定时任务 | ❌ | 新建 root span |
// 线程池透传 MDC 的最小实现
@Bean
TaskDecorator mdcDecorator() {
return runnable -> {
Map<String, String> ctx = MDC.getCopyOfContextMap();
return () -> {
if (ctx != null) MDC.setContextMap(ctx);
try { runnable.run(); } finally { MDC.clear(); }
};
};
}
5.2 跨服务传播
Brave/OTel 会自动注入 traceparent/X-B3-TraceId。自研 HTTP 客户端必须手动传播,否则链路在调用处断掉:
// 用 RestClient 时:Micrometer 的 Observation 会自动包裹;用裸 HttpClient 则要手动
String tp = tracer.currentSpan() != null
? "00-" + tracer.currentSpan().context().traceId() + "-" + tracer.currentSpan().context().spanId() + "-01"
: null;
if (tp != null) request.header("traceparent", tp);
6. 三个"跑不起来"的排查顺序
| 现象 | 先查 | 再看 |
|---|---|---|
日志里 traceId 为空 |
桥接依赖是否存在 | logging.pattern.level 是否含 %X{traceId} |
| Zipkin UI 空白 | management.zipkin.tracing.endpoint 是否可达 |
采样率是否 0;容器网络是否通 |
| 两个服务 traceId 不同 | 调用是否走同一 RestTemplate/RestClient/Feign |
是否跨线程/跨 MQ 断了上下文 |
| span 有但树是散的 | parent 传播是否丢失(自研客户端) | 是否在不同 trace 里各起 root span |
7. 常见误区
| 误区 | 纠正 |
|---|---|
| "加 Sleuth 就行" | Boot 3 用 Micrometer Tracing(§2) |
| "traceId 会自动进所有日志" | 只进被 Observation 包裹的线程;线程池/MQ 要手动透传 |
| "100% 采样更准" | 流量大时存储与网络成本线性上升,且会拖慢应用(A01) |
| "Zipkin 能长期存储" | 默认内存存储,重启即丢;生产要接 ES/Cassandra 或换后端 |
8. 自测
- 你的
build.gradle里有没有micrometer-tracing-bridge-*?没有 → traceId 一定为空。 - 拿一条线程池任务的日志,
traceId在不在?不在 → §5.1 的TaskDecorator。 - 你的采样率是多少?算出"一天能保留多少条错误链路",够不够排查。
9. 下一篇
S16-J03 · SkyWalking:Agent / OAP / UI 与服务拓扑 —— 不改代码的另一种接入方式,以及它带来的兼容性与开销代价。