基于:Spring Boot 3.x/4.x + Spring Cloud 2022.0+(Micrometer Tracing 1.x + Brave 桥接)、Spring Cloud Sleuth 3.1.x(Boot 2.x 时代,已停止演进)、Zipkin Server 3.6.x、Apache SkyWalking 11.x(Java Agent 9.x)
从 Trace/Span 模型与跨进程传播协议讲起,剖析 Sleuth 与 Micrometer Tracing 的核心机制、代码主线与埋点位置,对比 Zipkin 与 SkyWalking 两类后端的架构差异,并给出落地配置、迁移路径、扩展点与排障清单。
目录
- 核心理论:分布式链路追踪
- [1.1 为什么需要链路追踪](#1.1 为什么需要链路追踪)
- [1.2 核心概念:Trace / Span / SpanContext](#1.2 核心概念:Trace / Span / SpanContext)
- [1.3 四个组件的定位](#1.3 四个组件的定位)
- [设计思想与核心机制 ★★★](#设计思想与核心机制 ★★★)
- [2.1 Span 数据模型](#2.1 Span 数据模型)
- [2.2 跨进程传播协议:B3 与 W3C](#2.2 跨进程传播协议:B3 与 W3C)
- [2.3 采样策略](#2.3 采样策略)
- [2.4 MDC 日志关联](#2.4 MDC 日志关联)
- [2.5 Brave 与 OpenTelemetry 双桥接](#2.5 Brave 与 OpenTelemetry 双桥接)
- [2.6 从 Sleuth 到 Micrometer Tracing](#2.6 从 Sleuth 到 Micrometer Tracing)
- [主线流程剖析 ★★★](#主线流程剖析 ★★★)
- [3.1 请求入口:根 Span 的创建](#3.1 请求入口:根 Span 的创建)
- [3.2 出站调用:传播头的注入](#3.2 出站调用:传播头的注入)
- [3.3 跨线程与异步传播](#3.3 跨线程与异步传播)
- [3.4 异步批量上报 Zipkin](#3.4 异步批量上报 Zipkin)
- [3.5 全链路时序图](#3.5 全链路时序图)
- 核心类关系图
- [Zipkin 与 SkyWalking 后端对比](#Zipkin 与 SkyWalking 后端对比)
- [5.1 架构差异](#5.1 架构差异)
- [5.2 对比总表与选型建议](#5.2 对比总表与选型建议)
- [5.3 Docker Compose 快速部署](#5.3 Docker Compose 快速部署)
- 扩展点与常见问题
- [6.1 扩展点清单](#6.1 扩展点清单)
- [6.2 常见问题 FAQ](#6.2 常见问题 FAQ)
1. 核心理论:分布式链路追踪
1.1 为什么需要链路追踪
单体应用时代,一次请求的所有逻辑都在一个进程内:看日志、下断点、读调用栈,问题基本能定位。微服务把一次请求拆成了跨进程、跨机器、甚至跨机房的多跳调用------下单 一次可能穿过网关、订单、库存、支付、物流 5 个服务、10 余次 RPC。
| 痛点 | 单体时代 | 微服务时代 |
|---|---|---|
| 调用链可见性 | 方法调用栈一目了然 | 一次请求穿过多服务,链路不可见 ❌ |
| 故障定位 | 异常堆栈直接指向问题代码 | "下游超时"说不清是谁的锅,各服务日志互相甩锅 ❌ |
| 性能瓶颈 | Profiler 单进程分析 | 不知道 10 跳里到底哪一跳最慢 ❌ |
| 依赖梳理 | 无需关注 | 服务拓扑、调用关系靠人肉梳理 ❌ |
链路追踪属于可观测性三大支柱之一:
| 支柱 | 回答的问题 | 典型实现 |
|---|---|---|
| Logs 日志 | 系统里发生了什么(离散事件) | ELK、Loki |
| Metrics 指标 | 系统整体状态如何(聚合数值) | Prometheus、Micrometer |
| Traces 链路 | 一次请求的因果链与耗时分布(慢在哪一跳) | Zipkin、SkyWalking、Jaeger |
关键认知:Trace 就是"请求维度的分布式调用栈"------把跨进程的调用还原成一棵树,每个节点记录自己在整个请求中的起止时间、状态与上下游关系。
1.2 核心概念:Trace / Span / SpanContext
| 概念 | 说明 |
|---|---|
| Trace | 一次完整分布式请求的全局标识(traceId),由若干个 Span 构成的树 |
| Span | 基本工作单元:一次 RPC、一次 DB 查询、一段本地逻辑。记录名称、起止时间、标签、状态 |
| TraceContext(SpanContext) | traceId / spanId / parentId / 采样标记 / baggage 的最小上下文,跨进程传递的唯一载体 |
| Propagation | 把 TraceContext 写入(Inject)传输载体(HTTP 头、消息头)、读取(Extract)的一套协议 |
| Baggage | 随链路传播的业务 KV(如 userId、租户 ID),供下游业务使用,默认不落存储 |
| Sampling | 决定哪些 Trace 被采集上报的机制(全量上报成本太高) |
| Reporter | 采集端把 Span 批量异步发送到后端存储的组件 |
一次下单请求的 Trace 树(同一 traceId,parentId 串联):
#mermaid-svg-dx6Y9NvRfdOmXXX9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .error-icon{fill:#552222;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .marker.cross{stroke:#333333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dx6Y9NvRfdOmXXX9 p{margin:0;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .cluster-label text{fill:#333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .cluster-label span{color:#333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .cluster-label span p{background-color:transparent;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .label text,#mermaid-svg-dx6Y9NvRfdOmXXX9 span{fill:#333;color:#333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .node rect,#mermaid-svg-dx6Y9NvRfdOmXXX9 .node circle,#mermaid-svg-dx6Y9NvRfdOmXXX9 .node ellipse,#mermaid-svg-dx6Y9NvRfdOmXXX9 .node polygon,#mermaid-svg-dx6Y9NvRfdOmXXX9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .rough-node .label text,#mermaid-svg-dx6Y9NvRfdOmXXX9 .node .label text,#mermaid-svg-dx6Y9NvRfdOmXXX9 .image-shape .label,#mermaid-svg-dx6Y9NvRfdOmXXX9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .rough-node .label,#mermaid-svg-dx6Y9NvRfdOmXXX9 .node .label,#mermaid-svg-dx6Y9NvRfdOmXXX9 .image-shape .label,#mermaid-svg-dx6Y9NvRfdOmXXX9 .icon-shape .label{text-align:center;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .node.clickable{cursor:pointer;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .arrowheadPath{fill:#333333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dx6Y9NvRfdOmXXX9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dx6Y9NvRfdOmXXX9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dx6Y9NvRfdOmXXX9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .cluster text{fill:#333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .cluster span{color:#333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dx6Y9NvRfdOmXXX9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .icon-shape,#mermaid-svg-dx6Y9NvRfdOmXXX9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .icon-shape p,#mermaid-svg-dx6Y9NvRfdOmXXX9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .icon-shape .label rect,#mermaid-svg-dx6Y9NvRfdOmXXX9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dx6Y9NvRfdOmXXX9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dx6Y9NvRfdOmXXX9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dx6Y9NvRfdOmXXX9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Span 1:GET /api/order/1
order-service · kind=SERVER · 根 Span
Span 2:POST /inventory/deduct
order-service · kind=CLIENT
Span 3:POST /inventory/deduct
inventory-service · kind=SERVER
Span 4:SELECT ... FROM stock
inventory-service · kind=INTERNAL
1.3 四个组件的定位
本主题涉及四个组件,先分清各自在链路中的位置:
| 组件 | 定位 | 在链路中的角色 | 状态 |
|---|---|---|---|
| Sleuth | Spring 官方链路埋点库(Brave 的封装) | 采集端 SDK(进程内) | ⚠️ 停更于 3.1.x,Boot 3 弃用 |
| Micrometer Tracing | 厂商中立的追踪门面 API | 采集端 API 层(进程内) | ✅ Boot 3 起接替 Sleuth |
| Zipkin | 链路数据存储 + 查询 UI | 后端 | ✅ 活跃(3.6.x) |
| SkyWalking | 开源 APM 一体化平台 | Agent + 后端 OAP + UI | ✅ 活跃(11.x) |
一句话概括:Sleuth / Micrometer Tracing 是埋在应用进程里的"探针",负责生产 Span 并按协议在服务间传递上下文;Zipkin / SkyWalking 是后端的"仓库 + 望远镜",负责存储、聚合、展示。
2. 设计思想与核心机制 ★★★
2.1 Span 数据模型
一个 Span 的完整字段(Zipkin v2 模型与 Brave/OTel 基本对齐):
| 字段 | 说明 |
|---|---|
traceId |
全局唯一。W3C 标准 128 位(32 位 hex);B3 兼容 64/128 位 |
id(spanId) |
本 Span 在 Trace 内的唯一标识 |
parentId |
父 Span 的 spanId;根 Span 为空 |
name |
操作名,如 get /api/order/{id}、select stock |
timestamp / duration |
起止时间(微秒) |
kind |
方向:SERVER / CLIENT / PRODUCER / CONSUMER / INTERNAL |
tags |
标签 KV,如 http.method、http.status_code、db.statement |
events |
带时间戳的事件点(如 exception、ss(server send)) |
status |
OK / Error(OTel 语义) |
shared |
CLIENT/SERVER 是否共享同一个 Span(旧式 joined span,Boot 3 默认已弃用) |
kind 的语义:
| kind | 含义 | 谁创建 |
|---|---|---|
| SERVER | 服务端收到请求 | 被调方入口(inbound) |
| CLIENT | 客户端发起请求 | 调用方出口(outbound) |
| PRODUCER / CONSUMER | MQ 生产 / 消费 | 消息场景 |
| INTERNAL | 本地内部操作(定时任务、工具方法) | 自身 |
关键认知:一次 RPC = 两个 Span。调用方创建 CLIENT Span,被调方创建 SERVER Span;CLIENT 的 spanId 通过传播协议传给下游,作为下游的 parentId------链路由此串成树。
2.2 跨进程传播协议:B3 与 W3C
进程内上下文靠 ThreadLocal 即可,跨进程必须把 TraceContext 编码进 HTTP 头等载体。主流协议:
| 协议 | 头字段 | 说明 |
|---|---|---|
| B3 multi | X-B3-TraceId、X-B3-SpanId、X-B3-ParentSpanId、X-B3-Sampled、X-B3-Flags |
Zipkin 起源,头多但直观;Sleuth 3.x 的默认协议 |
| B3 single | b3: {traceId}-{spanId}-{sampled}-{parentId} |
单头格式,对 Kafka 等消息载体友好 |
| W3C Trace Context | traceparent、tracestate |
CNCF 标准,Boot 3 默认;跨语言互操作首选 |
| Jaeger | uber-trace-id |
Jaeger 系,仅在其生态内使用 |
W3C traceparent 结构解析:
http
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│ └────────────┬─────────────┘ └──────┬──────┘ └┬┘
版本 traceId(32 位 hex) spanId(16 位 hex) flags
00 = 128 位 traceId 01 = 已采样
配置传播协议(两代写法):
yaml
# Boot 2.x + Sleuth 3.x
spring:
sleuth:
propagation:
type: W3C # 可选:B3 / B3_SINGLE / W3C / JAEGER / OT_TRACER
yaml
# Boot 3+ / Micrometer Tracing
management:
tracing:
propagation:
type: w3c # 可选:b3 / b3_multi / w3c(默认 w3c)
关键认知 :采样决策在根 Span 创建时定案,随
traceparent的 flags 位逐跳传播,中途服务不得"补采样"------否则会出现链路断层(下游采了、上游没采,Zipkin 里拼不出完整树)。
2.3 采样策略
全量上报在大流量下会拖垮网络与存储(单 Trace 可能含上百 Span),所以必须采样:
| 策略 | 原理 | 适用场景 |
|---|---|---|
ProbabilityBasedSampler |
固定比例采样,如 10% | 默认策略,简单粗暴 |
RateLimitingSampler |
每秒最多 N 个 Trace | 平滑限流,防止突发流量打爆后端 |
BoundarySampler |
对 traceId 做 hash 与阈值比较 | 同分布、跨服务结果一致 |
Always / Never |
全采 / 全不采 | 调试 / 压测关闭 |
yaml
# Boot 2.x + Sleuth
spring:
sleuth:
sampler:
probability: 1.0 # 默认 0.1(10%)
yaml
# Boot 3+
management:
tracing:
sampling:
probability: 1.0 # 默认 0.10
自定义采样器(Sleuth 时代写法,Boot 3 可用 SamplerFunction 接口实现同样效果):
java
@Bean
public Sampler probabilityBasedSampler() {
// brave.sampler.Sampler:50% 概率采样
return ProbabilityBasedSampler.create(0.5f);
}
2.4 MDC 日志关联
链路追踪与日志结合才真正好用:把 traceId/spanId 放进 SLF4J 的 MDC,日志检索就能"顺藤摸瓜"。
| 机制 | Boot 2.x + Sleuth | Boot 3+ |
|---|---|---|
| 是否默认开启 | 是(spring.sleuth.log.slf4j.enabled=true) |
是(Slf4JEventListener 监听 Span 生命周期自动写入) |
| MDC 键 | traceId、spanId、spanExportable |
traceId、spanId |
| 自定义透传字段 | spring.sleuth.baggage.correlation-fields |
management.tracing.baggage.correlation.fields |
logback 输出格式示例:
xml
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{36} - %msg%n</pattern>
yaml
# Boot 3+:把自定义 baggage 字段(如 userId)也放进 MDC
management:
tracing:
baggage:
correlation:
enabled: true
fields: [userId, orderId]
跨服务日志串查:
bash
# 拿到一个 traceId,在全部服务的日志里把整条链路捞出来
grep 4bf92f3577b34da6a3ce929d0e0e4736 app-*.log
2.5 Brave 与 OpenTelemetry 双桥接
Micrometer Tracing 只是门面 API,不生产真正的 Span,必须挂一个"桥接器"(bridge):
#mermaid-svg-N8xZiOTysmBmvc4N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-N8xZiOTysmBmvc4N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-N8xZiOTysmBmvc4N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-N8xZiOTysmBmvc4N .error-icon{fill:#552222;}#mermaid-svg-N8xZiOTysmBmvc4N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-N8xZiOTysmBmvc4N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-N8xZiOTysmBmvc4N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-N8xZiOTysmBmvc4N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-N8xZiOTysmBmvc4N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-N8xZiOTysmBmvc4N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-N8xZiOTysmBmvc4N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-N8xZiOTysmBmvc4N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-N8xZiOTysmBmvc4N .marker.cross{stroke:#333333;}#mermaid-svg-N8xZiOTysmBmvc4N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-N8xZiOTysmBmvc4N p{margin:0;}#mermaid-svg-N8xZiOTysmBmvc4N .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-N8xZiOTysmBmvc4N .cluster-label text{fill:#333;}#mermaid-svg-N8xZiOTysmBmvc4N .cluster-label span{color:#333;}#mermaid-svg-N8xZiOTysmBmvc4N .cluster-label span p{background-color:transparent;}#mermaid-svg-N8xZiOTysmBmvc4N .label text,#mermaid-svg-N8xZiOTysmBmvc4N span{fill:#333;color:#333;}#mermaid-svg-N8xZiOTysmBmvc4N .node rect,#mermaid-svg-N8xZiOTysmBmvc4N .node circle,#mermaid-svg-N8xZiOTysmBmvc4N .node ellipse,#mermaid-svg-N8xZiOTysmBmvc4N .node polygon,#mermaid-svg-N8xZiOTysmBmvc4N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-N8xZiOTysmBmvc4N .rough-node .label text,#mermaid-svg-N8xZiOTysmBmvc4N .node .label text,#mermaid-svg-N8xZiOTysmBmvc4N .image-shape .label,#mermaid-svg-N8xZiOTysmBmvc4N .icon-shape .label{text-anchor:middle;}#mermaid-svg-N8xZiOTysmBmvc4N .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-N8xZiOTysmBmvc4N .rough-node .label,#mermaid-svg-N8xZiOTysmBmvc4N .node .label,#mermaid-svg-N8xZiOTysmBmvc4N .image-shape .label,#mermaid-svg-N8xZiOTysmBmvc4N .icon-shape .label{text-align:center;}#mermaid-svg-N8xZiOTysmBmvc4N .node.clickable{cursor:pointer;}#mermaid-svg-N8xZiOTysmBmvc4N .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-N8xZiOTysmBmvc4N .arrowheadPath{fill:#333333;}#mermaid-svg-N8xZiOTysmBmvc4N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-N8xZiOTysmBmvc4N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-N8xZiOTysmBmvc4N .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-N8xZiOTysmBmvc4N .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-N8xZiOTysmBmvc4N .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-N8xZiOTysmBmvc4N .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-N8xZiOTysmBmvc4N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-N8xZiOTysmBmvc4N .cluster text{fill:#333;}#mermaid-svg-N8xZiOTysmBmvc4N .cluster span{color:#333;}#mermaid-svg-N8xZiOTysmBmvc4N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-N8xZiOTysmBmvc4N .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-N8xZiOTysmBmvc4N rect.text{fill:none;stroke-width:0;}#mermaid-svg-N8xZiOTysmBmvc4N .icon-shape,#mermaid-svg-N8xZiOTysmBmvc4N .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-N8xZiOTysmBmvc4N .icon-shape p,#mermaid-svg-N8xZiOTysmBmvc4N .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-N8xZiOTysmBmvc4N .icon-shape .label rect,#mermaid-svg-N8xZiOTysmBmvc4N .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-N8xZiOTysmBmvc4N .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-N8xZiOTysmBmvc4N .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-N8xZiOTysmBmvc4N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务代码与 Spring 埋点
RestTemplate / WebClient / Feign / Observation
Micrometer Tracing
门面 API(Tracer / Span / Propagation)
Brave 桥接
micrometer-tracing-bridge-brave
OpenTelemetry 桥接
micrometer-tracing-bridge-otel
Zipkin Reporter
POST /api/v2/spans
OTLP Exporter
gRPC / HTTP
Zipkin Server
SkyWalking OAP
Grafana Tempo / Jaeger
| 维度 | Brave 桥接 | OpenTelemetry 桥接 |
|---|---|---|
| 出身 | Zipkin 社区(2014 起),Sleuth 的血缘 | CNCF 标准(2019 起),厂商中立 |
| 原生协议 | B3 | W3C Trace Context + OTLP |
| 导出目标 | Zipkin(zipkin-reporter-brave) |
任何支持 OTLP 的后端(含 SkyWalking OAP) |
| 体积与复杂度 | 轻、与 Zipkin 无缝 | 依赖稍多,但生态更标准 |
| 选择建议 | ⭐ 只在 Spring 生态内做链路追踪,选它最省事 | 需要跨语言统一标准、对接 OTel 生态(Agent、Collector)选它 |
关键认知:桥接器决定了"Span 的生产者"(Brave 的 Tracer 或 OTel 的 SDK),Reporter/Exporter 决定"消费者"。Boot 3 必须显式引入其中一个桥接器,追踪才会生效------Brave 桥接 + Zipkin 是 Spring 社区最常见组合。
2.6 从 Sleuth 到 Micrometer Tracing
演进时间线:
| 时间 | 事件 |
|---|---|
| 2015 ~ 2017 | Sleuth 1.x 随 Spring Cloud Angel ~ Camden 发布,封装 Brave 3 |
| 2018 ~ 2020 | Sleuth 2.x 适配 Boot 2.0,升级 Brave 5 |
| 2020 ~ 2022 | Sleuth 3.x 适配 Boot 2.4+,升级 Brave 5.13 |
| 2022-12 | Spring Cloud 2022.0(Kilburn)+ Boot 3.0 发布,Sleuth 正式弃用;Micrometer Tracing 1.0 独立门面接棒 |
| 之后 | Boot 3 埋点分散到各项目(spring-web、openfeign 等),由 Boot 自动装配;OpenRewrite 提供迁移配方 |
为什么弃用 Sleuth:
- 强绑定 Brave:Sleuth 的 API 与 Brave 类型深度耦合,换后端(OTel、自研)几乎不可能;
- 统一可观测性 API :与 Micrometer Metrics/Observation 融合,Trace 与 Metrics 共用
Observation抽象(一次埋点同时产出 Span + Timer + Counter); - Boot 3 基线重写:jakarta 命名空间、Java 17 基线,Sleuth 已无独立维护必要,能力移交 Micrometer 社区共建。
Sleuth → Boot 3 迁移对照表:
| 旧(Sleuth / Boot 2.x) | 新(Boot 3 / Micrometer Tracing) |
|---|---|
依赖 spring-cloud-starter-sleuth |
micrometer-tracing-bridge-brave(或 -otel)+ 显式加 spring-boot-starter-actuator |
依赖 spring-cloud-sleuth-zipkin |
zipkin-reporter-brave |
spring.sleuth.enabled |
management.tracing.enabled |
spring.sleuth.sampler.probability |
management.tracing.sampling.probability |
spring.sleuth.propagation.type |
management.tracing.propagation.type |
spring.zipkin.base-url |
management.zipkin.tracing.endpoint |
spring.sleuth.baggage.correlation-fields |
management.tracing.baggage.correlation.fields |
包 org.springframework.cloud.sleuth.* |
包 io.micrometer.tracing.*(API 基本同名复制) |
@NewSpan / @SpanTag 注解 |
@Observed / Observation API |
| Feign 由 Sleuth 自动增强 | 引入 feign-micrometer,Feign 走 Observation |
迁移可借助 OpenRewrite 官方配方 MigrateSleuthToOpenTelemetry 半自动化完成(见 参考资料)。
3. 主线流程剖析 ★★★
以一次 HTTP 请求穿过 order-service → inventory-service 为例,拆开四段主线。
3.1 请求入口:根 Span 的创建
Boot 2.x 由 Sleuth 的 Filter 承担(MVC 用 LazyTracingFilter + TraceAsyncListener,WebFlux 用 TraceWebFilter);Boot 3 起该职责分散到 spring-web 的 Observation 埋点(ServerRequestObservationContext),由 Brave 桥接器的 ObservationHandler 转译为 Span。机制一致,以 Sleuth 的 TraceWebFilter 为例:
java
// org.springframework.cloud.sleuth.instrument.web.TraceWebFilter 简化流程
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// ① 从请求头提取 TraceContext(Extract)
// 有 traceparent → 沿用上游 traceId,parentId = 上游 spanId
// 没有 → 生成全新 traceId,本 Span 即根 Span
TraceContextOrSamplingFlags extracted =
tracing.propagation().extractor(GETTER).extract(exchange.getRequest());
// ② 创建 SERVER Span(采样决策在此定案)
Span span = handler.handleReceive(extracted, exchange.getRequest());
// ③ 绑定当前线程(ThreadLocal + Scope 栈),后续同线程埋点共享该上下文
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
// ④ 放行过滤链,业务正常执行
return chain.filter(exchange)
// ⑤ 响应时关闭 Span,记录状态码与耗时
.doFinally(s -> handler.handleSend(exchange.getResponse(), span.error(), span));
}
}
关键认知 :traceId 在这里"定型"------要么继承上游,要么新建;采样决策也在这里通过 flags 位定案。过滤链上任何位置的业务代码都能用
tracer.currentSpan()拿到当前 Span。
3.2 出站调用:传播头的注入
调用下游时,埋点组件给本 Span 挂一个 CLIENT 子 Span,并把上下文注入请求头:
| 调用组件 | Boot 2.x + Sleuth | Boot 3+(统一走 Observation) |
|---|---|---|
| RestTemplate | TracingClientHttpRequestInterceptor |
RestTemplateObservationInterceptor |
| WebClient | TraceExchangeFilterFunction |
ClientHttpObservationInterceptor |
| OpenFeign | TracingFeignClient(FeignContext 包装) |
feign-micrometer + Observation |
| Kafka / MQ | TracingProducerInterceptor 等 |
埋点随 spring-kafka 等各自项目提供 |
以 Sleuth 的 TracingClientHttpRequestInterceptor 为例:
java
// 简化流程:RestTemplate 出站埋点
public ClientHttpResponse intercept(HttpRequest req, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// ① 创建 CLIENT Span,并把上下文 Inject 进请求头(X-B3-* / traceparent)
Span span = handler.handleSend(injector, req);
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
// ② 真实发起 HTTP 调用,下游从请求头 Extract 出同一 traceId
ClientHttpResponse resp = execution.execute(req, body);
handler.handleReceive(resp, span); // ③ 记录响应码,关闭 Span
return resp;
} catch (Exception e) {
span.error(e); // ④ 异常打上 error 标签
throw e;
}
}
关键认知:CLIENT Span 的 spanId 会作为下游 SERVER Span 的 parentId 写进请求头------这就是 2.1 节"一次 RPC 两个 Span"的代码实现。
3.3 跨线程与异步传播
Span 上下文存在 ThreadLocal 里,@Async、线程池、CompletableFuture、Reactor 切换线程后上下文会丢失------必须显式传播:
| 场景 | Boot 2.x + Sleuth | Boot 3+ |
|---|---|---|
| Executor / 线程池 | 用 LazyTraceExecutor、LazyTraceThreadPoolTaskExecutor 包装 |
ContextPropagator + 包装 Executor |
| 异步任务 | TraceableExecutorService |
ObservationThreadLocalAccessor 自动跨线程恢复 |
手动 new Thread |
❌ 不会自动传播 | ❌ 不会自动传播,需手动 wrap |
Boot 3 手动传播(io.micrometer.context.ContextSnapshot):
java
// 主线程捕获上下文快照
ContextSnapshot snapshot = ContextSnapshotFactory.builder().build().captureAll();
// 子线程执行时恢复
executor.execute(snapshot.wrap(() -> {
// 这里能拿到 traceId / spanId / baggage,MDC 也会被恢复
log.info("子线程继续写日志,traceId 不断");
doWork();
}));
关键认知 :所有"自动传播"都是通过包装器实现的,
new Thread裸线程没有任何魔法------子线程日志丢了 traceId,先检查线程是怎么创建的。
3.4 异步批量上报 Zipkin
Span 结束不代表立刻发网络请求,而是走异步批量上报管线:
#mermaid-svg-9QQ3b0epY6BNxYIN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9QQ3b0epY6BNxYIN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9QQ3b0epY6BNxYIN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9QQ3b0epY6BNxYIN .error-icon{fill:#552222;}#mermaid-svg-9QQ3b0epY6BNxYIN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9QQ3b0epY6BNxYIN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9QQ3b0epY6BNxYIN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9QQ3b0epY6BNxYIN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9QQ3b0epY6BNxYIN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9QQ3b0epY6BNxYIN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9QQ3b0epY6BNxYIN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9QQ3b0epY6BNxYIN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9QQ3b0epY6BNxYIN .marker.cross{stroke:#333333;}#mermaid-svg-9QQ3b0epY6BNxYIN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9QQ3b0epY6BNxYIN p{margin:0;}#mermaid-svg-9QQ3b0epY6BNxYIN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9QQ3b0epY6BNxYIN .cluster-label text{fill:#333;}#mermaid-svg-9QQ3b0epY6BNxYIN .cluster-label span{color:#333;}#mermaid-svg-9QQ3b0epY6BNxYIN .cluster-label span p{background-color:transparent;}#mermaid-svg-9QQ3b0epY6BNxYIN .label text,#mermaid-svg-9QQ3b0epY6BNxYIN span{fill:#333;color:#333;}#mermaid-svg-9QQ3b0epY6BNxYIN .node rect,#mermaid-svg-9QQ3b0epY6BNxYIN .node circle,#mermaid-svg-9QQ3b0epY6BNxYIN .node ellipse,#mermaid-svg-9QQ3b0epY6BNxYIN .node polygon,#mermaid-svg-9QQ3b0epY6BNxYIN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9QQ3b0epY6BNxYIN .rough-node .label text,#mermaid-svg-9QQ3b0epY6BNxYIN .node .label text,#mermaid-svg-9QQ3b0epY6BNxYIN .image-shape .label,#mermaid-svg-9QQ3b0epY6BNxYIN .icon-shape .label{text-anchor:middle;}#mermaid-svg-9QQ3b0epY6BNxYIN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9QQ3b0epY6BNxYIN .rough-node .label,#mermaid-svg-9QQ3b0epY6BNxYIN .node .label,#mermaid-svg-9QQ3b0epY6BNxYIN .image-shape .label,#mermaid-svg-9QQ3b0epY6BNxYIN .icon-shape .label{text-align:center;}#mermaid-svg-9QQ3b0epY6BNxYIN .node.clickable{cursor:pointer;}#mermaid-svg-9QQ3b0epY6BNxYIN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9QQ3b0epY6BNxYIN .arrowheadPath{fill:#333333;}#mermaid-svg-9QQ3b0epY6BNxYIN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9QQ3b0epY6BNxYIN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9QQ3b0epY6BNxYIN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9QQ3b0epY6BNxYIN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9QQ3b0epY6BNxYIN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9QQ3b0epY6BNxYIN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9QQ3b0epY6BNxYIN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9QQ3b0epY6BNxYIN .cluster text{fill:#333;}#mermaid-svg-9QQ3b0epY6BNxYIN .cluster span{color:#333;}#mermaid-svg-9QQ3b0epY6BNxYIN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9QQ3b0epY6BNxYIN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9QQ3b0epY6BNxYIN rect.text{fill:none;stroke-width:0;}#mermaid-svg-9QQ3b0epY6BNxYIN .icon-shape,#mermaid-svg-9QQ3b0epY6BNxYIN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9QQ3b0epY6BNxYIN .icon-shape p,#mermaid-svg-9QQ3b0epY6BNxYIN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9QQ3b0epY6BNxYIN .icon-shape .label rect,#mermaid-svg-9QQ3b0epY6BNxYIN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9QQ3b0epY6BNxYIN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9QQ3b0epY6BNxYIN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9QQ3b0epY6BNxYIN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} POST /api/v2/spans
JSON v2 数组
span.end()
AsyncZipkinSpanHandler
SpanHandler 回调
AsyncReporter 有界队列
默认队列 1 万条
批量 500KB / 1s 超时触发
Sender
URLConnectionSender / OkHttpSender
Zipkin Collector
存储:ES / MySQL / Cassandra / 内存
上报的 JSON(v2 协议,一个 Trace 的若干 Span 拼一个数组):
json
[{
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"id": "00f067aa0ba902b7",
"parentId": "85cb1f64c1ddfd55",
"name": "get /api/order/{id}",
"timestamp": 1755424512012345,
"duration": 34567,
"kind": "SERVER",
"localEndpoint": { "serviceName": "order-service" },
"tags": { "http.method": "GET", "http.path": "/api/order/1" }
}]
接入配置(Boot 3+):
yaml
management:
tracing:
sampling:
probability: 1.0 # 演示环境全量
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans # 默认值即此地址
logging:
pattern:
level: "%5p [%X{traceId:-},%X{spanId:-}]"
3.5 全链路时序图
Zipkin inventory-service order-service Gateway 客户端 Zipkin inventory-service order-service Gateway 客户端 #mermaid-svg-o28CdQAkhKJsDIXj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-o28CdQAkhKJsDIXj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-o28CdQAkhKJsDIXj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-o28CdQAkhKJsDIXj .error-icon{fill:#552222;}#mermaid-svg-o28CdQAkhKJsDIXj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o28CdQAkhKJsDIXj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-o28CdQAkhKJsDIXj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o28CdQAkhKJsDIXj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o28CdQAkhKJsDIXj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-o28CdQAkhKJsDIXj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o28CdQAkhKJsDIXj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o28CdQAkhKJsDIXj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o28CdQAkhKJsDIXj .marker.cross{stroke:#333333;}#mermaid-svg-o28CdQAkhKJsDIXj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o28CdQAkhKJsDIXj p{margin:0;}#mermaid-svg-o28CdQAkhKJsDIXj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-o28CdQAkhKJsDIXj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-o28CdQAkhKJsDIXj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-o28CdQAkhKJsDIXj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-o28CdQAkhKJsDIXj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-o28CdQAkhKJsDIXj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-o28CdQAkhKJsDIXj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-o28CdQAkhKJsDIXj .sequenceNumber{fill:white;}#mermaid-svg-o28CdQAkhKJsDIXj #sequencenumber{fill:#333;}#mermaid-svg-o28CdQAkhKJsDIXj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-o28CdQAkhKJsDIXj .messageText{fill:#333;stroke:none;}#mermaid-svg-o28CdQAkhKJsDIXj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-o28CdQAkhKJsDIXj .labelText,#mermaid-svg-o28CdQAkhKJsDIXj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-o28CdQAkhKJsDIXj .loopText,#mermaid-svg-o28CdQAkhKJsDIXj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-o28CdQAkhKJsDIXj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-o28CdQAkhKJsDIXj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-o28CdQAkhKJsDIXj .noteText,#mermaid-svg-o28CdQAkhKJsDIXj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-o28CdQAkhKJsDIXj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-o28CdQAkhKJsDIXj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-o28CdQAkhKJsDIXj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-o28CdQAkhKJsDIXj .actorPopupMenu{position:absolute;}#mermaid-svg-o28CdQAkhKJsDIXj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-o28CdQAkhKJsDIXj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-o28CdQAkhKJsDIXj .actor-man circle,#mermaid-svg-o28CdQAkhKJsDIXj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-o28CdQAkhKJsDIXj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Filter 提取无上游上下文 创建根 Span:traceId=4bf9... spanId=85cb... Extract 出 traceId 创建 SERVER Span,parentId=85cb... 同上,parentId=a2f3... 各服务 Span 结束,AsyncReporter 批量异步上报 按 traceId 聚合还原整棵树 计算每跳耗时、绘制依赖图 GET /api/order/1 1 GET /order/1 traceparent: 00-4bf9...-85cb...-01 2 POST /inventory/deduct traceparent: 00-4bf9...-a2f3...-01 3 200 OK 4 200 OK 5 200 OK 6 POST /api/v2/spans(JSON) 7 POST /api/v2/spans(JSON) 8 POST /api/v2/spans(JSON) 9
4. 核心类关系图
Micrometer Tracing 门面 + Brave 桥接的类关系:
#mermaid-svg-dh1bAiZNpPOfaRKC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dh1bAiZNpPOfaRKC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dh1bAiZNpPOfaRKC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dh1bAiZNpPOfaRKC .error-icon{fill:#552222;}#mermaid-svg-dh1bAiZNpPOfaRKC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dh1bAiZNpPOfaRKC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dh1bAiZNpPOfaRKC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dh1bAiZNpPOfaRKC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dh1bAiZNpPOfaRKC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dh1bAiZNpPOfaRKC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dh1bAiZNpPOfaRKC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dh1bAiZNpPOfaRKC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dh1bAiZNpPOfaRKC .marker.cross{stroke:#333333;}#mermaid-svg-dh1bAiZNpPOfaRKC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dh1bAiZNpPOfaRKC p{margin:0;}#mermaid-svg-dh1bAiZNpPOfaRKC g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-dh1bAiZNpPOfaRKC g.classGroup text .title{font-weight:bolder;}#mermaid-svg-dh1bAiZNpPOfaRKC .cluster-label text{fill:#333;}#mermaid-svg-dh1bAiZNpPOfaRKC .cluster-label span{color:#333;}#mermaid-svg-dh1bAiZNpPOfaRKC .cluster-label span p{background-color:transparent;}#mermaid-svg-dh1bAiZNpPOfaRKC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dh1bAiZNpPOfaRKC .cluster text{fill:#333;}#mermaid-svg-dh1bAiZNpPOfaRKC .cluster span{color:#333;}#mermaid-svg-dh1bAiZNpPOfaRKC .nodeLabel,#mermaid-svg-dh1bAiZNpPOfaRKC .edgeLabel{color:#131300;}#mermaid-svg-dh1bAiZNpPOfaRKC .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-dh1bAiZNpPOfaRKC .label text{fill:#131300;}#mermaid-svg-dh1bAiZNpPOfaRKC .labelBkg{background:#ECECFF;}#mermaid-svg-dh1bAiZNpPOfaRKC .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-dh1bAiZNpPOfaRKC .classTitle{font-weight:bolder;}#mermaid-svg-dh1bAiZNpPOfaRKC .node rect,#mermaid-svg-dh1bAiZNpPOfaRKC .node circle,#mermaid-svg-dh1bAiZNpPOfaRKC .node ellipse,#mermaid-svg-dh1bAiZNpPOfaRKC .node polygon,#mermaid-svg-dh1bAiZNpPOfaRKC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dh1bAiZNpPOfaRKC .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC g.clickable{cursor:pointer;}#mermaid-svg-dh1bAiZNpPOfaRKC g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-dh1bAiZNpPOfaRKC g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-dh1bAiZNpPOfaRKC .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-dh1bAiZNpPOfaRKC .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-dh1bAiZNpPOfaRKC .dashed-line{stroke-dasharray:3;}#mermaid-svg-dh1bAiZNpPOfaRKC .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-dh1bAiZNpPOfaRKC #compositionStart,#mermaid-svg-dh1bAiZNpPOfaRKC .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC #compositionEnd,#mermaid-svg-dh1bAiZNpPOfaRKC .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC #dependencyStart,#mermaid-svg-dh1bAiZNpPOfaRKC .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC #dependencyStart,#mermaid-svg-dh1bAiZNpPOfaRKC .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC #extensionStart,#mermaid-svg-dh1bAiZNpPOfaRKC .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC #extensionEnd,#mermaid-svg-dh1bAiZNpPOfaRKC .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC #aggregationStart,#mermaid-svg-dh1bAiZNpPOfaRKC .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC #aggregationEnd,#mermaid-svg-dh1bAiZNpPOfaRKC .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC #lollipopStart,#mermaid-svg-dh1bAiZNpPOfaRKC .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC #lollipopEnd,#mermaid-svg-dh1bAiZNpPOfaRKC .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-dh1bAiZNpPOfaRKC .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-dh1bAiZNpPOfaRKC .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dh1bAiZNpPOfaRKC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dh1bAiZNpPOfaRKC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dh1bAiZNpPOfaRKC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建
线程绑定
注入提取
结束时上报
<<interface>>
Tracer
+nextSpan() : Span
+startScopedSpan() : ScopedSpan
+currentSpan() : Span
+withSpanInScope() : SpanInScope
<<interface>>
Span
+name() tag() event()
+end() error()
+context() : TraceContext
<<interface>>
TraceContext
+traceId() spanId() parentId()
+sampled() : Boolean
<<interface>>
Propagation
+inject(TraceContext, Setter)
+extract(Getter) : TraceContext
<<interface>>
CurrentTraceContext
+newScope(TraceContext) : Scope
+get() : TraceContext
<<interface>>
Sampler
+isSampled(Long traceId) : boolean
<<interface>>
Reporter
+report(Span)
BraveTracer
BraveSpan
BravePropagator
ProbabilityBasedSampler
AsyncZipkinSpanHandler
关键类一览:
| 类 | 职责 | 所属库 |
|---|---|---|
TraceWebFilter / LazyTracingFilter |
服务端入口埋点(WebFlux / MVC) | spring-cloud-sleuth |
TracingClientHttpRequestInterceptor |
RestTemplate 出站埋点 | spring-cloud-sleuth |
TraceExchangeFilterFunction |
WebClient 出站埋点 | spring-cloud-sleuth |
TracingFeignClient |
Feign 客户端包装 | spring-cloud-sleuth |
LazyTraceExecutor 系列 |
线程池上下文传播包装 | spring-cloud-sleuth |
Slf4JEventListener |
Span 生命周期 → MDC | micrometer-tracing |
BraveAutoConfiguration / ZipkinAutoConfiguration |
Boot 3 自动装配(Tracing、Reporter Bean) | spring-boot-actuator-autoconfigure |
AsyncZipkinSpanHandler |
Brave Span → Zipkin 批量上报 | zipkin-reporter-brave |
brave.Tracing(门面对象) |
组装 Tracer / Propagation / Sampler / Reporter 的总线 | brave |
5. Zipkin 与 SkyWalking 后端对比
5.1 架构差异
两者最本质的区别在采集方式:Zipkin 靠 SDK 埋点(Sleuth/Brave Reporter),SkyWalking 靠 JavaAgent 字节码增强(无侵入)。
#mermaid-svg-gIPnniKGakeOImak{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gIPnniKGakeOImak .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gIPnniKGakeOImak .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gIPnniKGakeOImak .error-icon{fill:#552222;}#mermaid-svg-gIPnniKGakeOImak .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gIPnniKGakeOImak .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gIPnniKGakeOImak .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gIPnniKGakeOImak .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gIPnniKGakeOImak .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gIPnniKGakeOImak .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gIPnniKGakeOImak .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gIPnniKGakeOImak .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gIPnniKGakeOImak .marker.cross{stroke:#333333;}#mermaid-svg-gIPnniKGakeOImak svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gIPnniKGakeOImak p{margin:0;}#mermaid-svg-gIPnniKGakeOImak .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gIPnniKGakeOImak .cluster-label text{fill:#333;}#mermaid-svg-gIPnniKGakeOImak .cluster-label span{color:#333;}#mermaid-svg-gIPnniKGakeOImak .cluster-label span p{background-color:transparent;}#mermaid-svg-gIPnniKGakeOImak .label text,#mermaid-svg-gIPnniKGakeOImak span{fill:#333;color:#333;}#mermaid-svg-gIPnniKGakeOImak .node rect,#mermaid-svg-gIPnniKGakeOImak .node circle,#mermaid-svg-gIPnniKGakeOImak .node ellipse,#mermaid-svg-gIPnniKGakeOImak .node polygon,#mermaid-svg-gIPnniKGakeOImak .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gIPnniKGakeOImak .rough-node .label text,#mermaid-svg-gIPnniKGakeOImak .node .label text,#mermaid-svg-gIPnniKGakeOImak .image-shape .label,#mermaid-svg-gIPnniKGakeOImak .icon-shape .label{text-anchor:middle;}#mermaid-svg-gIPnniKGakeOImak .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gIPnniKGakeOImak .rough-node .label,#mermaid-svg-gIPnniKGakeOImak .node .label,#mermaid-svg-gIPnniKGakeOImak .image-shape .label,#mermaid-svg-gIPnniKGakeOImak .icon-shape .label{text-align:center;}#mermaid-svg-gIPnniKGakeOImak .node.clickable{cursor:pointer;}#mermaid-svg-gIPnniKGakeOImak .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gIPnniKGakeOImak .arrowheadPath{fill:#333333;}#mermaid-svg-gIPnniKGakeOImak .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gIPnniKGakeOImak .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gIPnniKGakeOImak .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gIPnniKGakeOImak .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gIPnniKGakeOImak .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gIPnniKGakeOImak .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gIPnniKGakeOImak .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gIPnniKGakeOImak .cluster text{fill:#333;}#mermaid-svg-gIPnniKGakeOImak .cluster span{color:#333;}#mermaid-svg-gIPnniKGakeOImak div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gIPnniKGakeOImak .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gIPnniKGakeOImak rect.text{fill:none;stroke-width:0;}#mermaid-svg-gIPnniKGakeOImak .icon-shape,#mermaid-svg-gIPnniKGakeOImak .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gIPnniKGakeOImak .icon-shape p,#mermaid-svg-gIPnniKGakeOImak .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gIPnniKGakeOImak .icon-shape .label rect,#mermaid-svg-gIPnniKGakeOImak .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gIPnniKGakeOImak .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gIPnniKGakeOImak .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gIPnniKGakeOImak :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 方案 B:SkyWalking(Agent 无侵入)
gRPC :11800
Segment 数据
Java 应用
-javaagent:skywalking-agent.jar
ByteBuddy 字节码增强
OAP 后端
接收 / 分析 / 聚合 / 告警
ES / BanyanDB / H2
SkyWalking UI :8080
方案 A:Zipkin(SDK 埋点)
HTTP POST /api/v2/spans
Java 应用
引入 Sleuth / Brave 依赖
Zipkin Collector
ES / MySQL / Cassandra
Zipkin UI :9411
- Zipkin:遵守 Dapper 经典模型,协议标准(B3/W3C + v2 JSON),后端轻量。但要求每个服务引入埋点依赖、链路数据之外什么都没有;
- SkyWalking :
skywalking-agent.jar以-javaagent挂载,动态增强类(无需改一行代码),还顺带采集 JVM 指标、生成服务拓扑、告警、Profiling。代价是 OAP 后端更重,且 SkyWalking 自有协议(sw8)为主、跨语言生态不如 OTel 开放(9.x 起已内置 OTLP 接收器补齐)。
5.2 对比总表与选型建议
| 维度 | Zipkin | SkyWalking |
|---|---|---|
| 定位 | 专注分布式链路追踪 | 全栈 APM(Trace + Metrics + Logs) |
| 埋点方式 | SDK 埋点(Spring 集成自动) | JavaAgent 无侵入(也支持 SDK/OTLP 接入) |
| 数据协议 | B3 / W3C 标准,v2 JSON | 私有 sw8 + 兼容 OTLP 接收 |
| 存储 | ES / MySQL / Cassandra / 内存 | ES / BanyanDB / H2 |
| 服务端资源 | 轻量,单容器可跑 | OAP 较重(聚合 + 指标分析) |
| 应用侧改动 | 加 starter + 配置 | 只加 JVM 启动参数 ⭐ |
| 附加能力 | 无(可配 Grafana 等外挂) | 服务拓扑、JVM 指标、告警、Profiling、eBPF |
| 协议开放性 | 标准协议,跨语言友好 | 自有协议为主,OTLP 补齐中 |
| 适用场景 | Spring 生态快速接入、链路追踪单一诉求 | 大型 Java 微服务体系的整体可观测性平台 |
关键认知(选型) :只做链路追踪、团队以 Spring 为主、想要标准协议 → Zipkin (配合 Sleuth/Micrometer Tracing 最顺);想要无侵入、顺便拿 JVM 指标与拓扑告警 → SkyWalking。两者不互斥,也可 Zipkin 看链路 + Prometheus/Grafana 看指标的组合,或统一走 OTLP 由 Collector 分发。
5.3 Docker Compose 快速部署
yaml
# docker-compose.yml:Zipkin + SkyWalking + ES 一键起(演示环境)
services:
zipkin:
image: openzipkin/zipkin:3.6
ports: ["9411:9411"]
environment:
- STORAGE_TYPE=elasticsearch
- ES_HOSTS=http://es:9200
oap:
image: apache/skywalking-oap-server:10.4.0
environment:
- SW_STORAGE=elasticsearch
- SW_STORAGE_ES_CLUSTER_NODES=es:9200
- SW_CORE_RECORD_DATA_TTL=3 # 明细记录保留 3 天
- SW_OTEL_RECEIVER_ENABLED=true # 开启 OTLP 接收,可接 Micrometer Tracing
ports: ["11800:11800", "12800:12800"]
ui:
image: apache/skywalking-ui:10.4.0
environment:
- SW_OAP_ADDRESS=http://oap:12800
ports: ["8080:8080"]
es:
image: docker.elastic.co/elasticsearch/elasticsearch:8.14.0
environment: ["discovery.type=single-node", "xpack.security.enabled=false"]
ports: ["9200:9200"]
应用接入 SkyWalking(不改代码):
bash
java -javaagent:/opt/skywalking-agent/skywalking-agent.jar \
-Dskywalking.agent.service_name=order-service \
-Dskywalking.collector.backend_service=oap:11800 \
-jar order-service.jar
应用接入 Zipkin(Boot 3+,依赖 micrometer-tracing-bridge-brave):
yaml
management:
tracing:
sampling:
probability: 1.0
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
Micrometer Tracing 直连 SkyWalking(OTel 桥接 + OTLP,OAP 需开启 OTLP 接收器):
yaml
management:
otlp:
tracing:
endpoint: http://oap:12800/v1/traces # OAP 的 OTLP/HTTP 地址
6. 扩展点与常见问题
6.1 扩展点清单
| 扩展点 | Boot 2.x + Sleuth | Boot 3+ |
|---|---|---|
| 自定义采样 | brave.sampler.Sampler Bean |
management.tracing.sampling.probability / 自定义 SamplerFunction |
| 自定义上报 / 多后端 | SpanReporter、SpanAdjuster Bean |
注册多个 SpanHandler Bean(同时发 Zipkin + 自研) |
| 传播字段扩展 | BaggagePropagation.newFactoryBuilder() |
BaggagePropagationCustomizer |
| Baggage 白名单 | spring.sleuth.baggage.remote-fields |
management.tracing.baggage.remote-fields |
| 日志关联字段 | spring.sleuth.baggage.correlation-fields |
management.tracing.baggage.correlation.fields |
| 过滤器顺序 | spring.sleuth.web.filter-order |
自定义 Filter 注册顺序(Observation 体系接管) |
| SkyWalking 侧 | 自定义 Java Agent 插件(拦截类声明 + 增强逻辑) | 同左 |
6.2 常见问题 FAQ
Q1:Zipkin 里查不到任何 Trace?
按顺序排查:① management.tracing.sampling.probability 是否为 0;② Zipkin 地址是否可达(curl localhost:9411/api/v2/services);③ 依赖是否齐全------Boot 3 必须显式加 spring-boot-starter-actuator 和桥接器;④ 日志里 [%X{traceId:-}] 是否已有值(没有说明埋点根本没生效,先看 ③);⑤ 时区/时间范围筛选是否过窄。
Q2:异步线程日志里 traceId 为空?
线程上下文不会自动跟过去。用 LazyTraceThreadPoolTaskExecutor(Boot 2)或 ContextSnapshot.wrap()(Boot 3)显式传播;裸 new Thread 无解,必须手动 wrap。见 [3.3 跨线程与异步传播](#3.3 跨线程与异步传播)。
Q3:下游服务收不到 traceparent 头,链路串不起来?
① 检查客户端是否走了埋点包装(自己 new RestTemplate() 且没加 interceptor、绕过 FeignContext 手写 client 都会漏);② 上下游传播协议配置不一致(一个 W3C 一个 B3);③ 出站请求被框架二次封装丢头。先在日志里确认下游 traceId 是否与上游一致。
Q4:采样率怎么定?
高流量生产建议 probability 从 0.01~0.1 起步,配合 RateLimitingSampler 平滑限流;关键链路(支付、下单)可通过入口强制采样;调试时用 debug flags 强制单条 Trace 全采。
Q5:Boot 2 + Sleuth 项目如何迁到 Boot 3?
按 [2.6 节迁移对照表](#2.6 节迁移对照表) 换依赖 + 改配置前缀;没有自定义埋点的项目几乎零代码改动;有自定义埋点的把 org.springframework.cloud.sleuth 包替换为 io.micrometer.tracing,或用 OpenRewrite 配方 MigrateSleuthToOpenTelemetry 半自动迁移。
Q6:Zipkin 和 SkyWalking 选哪个?
见 [5.2 选型结论](#5.2 选型结论):链路追踪单一诉求、Spring 生态为主 → Zipkin;要无侵入 + 指标 + 拓扑 + 告警 → SkyWalking。两者也可共存(各自独立存储,互不干扰)。
Q7:Trace 数据膨胀,存储爆了?
三招:① 降采样率(见 Q4);② 设 TTL 清理------SkyWalking 用 SW_CORE_RECORD_DATA_TTL / SW_CORE_TRACE_DATA_TTL(默认 3 天),Zipkin 走 ES 索引生命周期管理(ILM)定期删索引;③ ES 冷热分层,热数据 SSD、历史数据归档。