Skip to content

Sleuth 详解:链路追踪

  Spring Cloud Sleuth 是 Spring Cloud 生态中的分布式链路追踪组件。它自动为每个请求生成全局唯一的 TraceId 和 SpanId,在服务间调用时自动透传,最终将链路数据上报到 Zipkin 等可视化平台,让你在几十个微服务中快速定位性能瓶颈和故障点。

重要提示: Spring Boot 3.x / Spring Cloud 2022.x 起,Sleuth 已被 Micrometer Tracing 取代。本章以 Sleuth 为主讲解核心概念和原理(两者概念一致),最后一节专讲迁移。如果你用的是 Spring Boot 3.x,详见 Micrometer Tracing 详解

一、为什么需要链路追踪?

1.1 没有链路追踪的困境

一个请求经过 5 个微服务:

  User → Gateway → order-service → inventory-service → DB
                                 → payment-service  → 第三方支付
                                 → sms-service      → 短信网关

用户反馈:"下单很慢,等了 5 秒"

没有链路追踪时:
  ├── 查 order-service 日志:RT=50ms,正常
  ├── 查 inventory-service 日志:RT=30ms,正常
  ├── 查 payment-service 日志:RT=40ms,正常
  ├── 查 sms-service 日志:RT=20ms,正常
  └── 50+30+40+20=140ms,加起来不到 5 秒啊?

  问题:你不知道哪个环节在"等待",哪个环节在"并行"
  你也不知道这个请求到底经过了哪些服务、每段耗时多少

有链路追踪时:
  Gateway ──2ms──→ order-service ──30ms──→ inventory-service
                                   ├──4.5s──→ payment-service  ← 找到了!
                                   └──20ms──→ sms-service

  一眼看出:payment-service 花了 4.5 秒,其他都正常
  问题定位:从"不知道从哪查"变成"一眼看到"

1.2 链路追踪解决什么问题

问题传统方式用链路追踪
请求经过哪些服务?猜,或者逐个服务查日志链路图一目了然
哪个服务慢?各服务日志里的 RT 拼起来直接看到每个 Span 的耗时
调用顺序?看代码,或者猜父子 Span 关系清晰展示
某个 TraceId 的完整日志?逐个服务 grep,可能漏一个 TraceId 查出所有日志
错误发生在哪个环节?逐个服务查错误日志链路中红色高亮故障节点

二、核心概念

2.1 Trace 和 Span

一次完整的用户请求链路(Trace):

  TraceId: a1b2c3d4e5f6  ← 贯穿整个链路的全局唯一 ID

  ┌──────────────────────────────────────────────────────────────────┐
  │  Span1: Gateway (入口)                                           │
  │  SpanId: aaa, ParentSpanId: null                                 │
  │  Kind: SERVER, Duration: 2ms                                     │
  │                                                                  │
  │  ┌────────────────────────────────────────────────────────────┐  │
  │  │  Span2: order-service (中间)                                │  │
  │  │  SpanId: bbb, ParentSpanId: aaa                             │  │
  │  │  Kind: SERVER, Duration: 80ms                               │  │
  │  │                                                            │  │
  │  │  ┌──────────────────────┐  ┌──────────────────────┐        │  │
  │  │  │ Span3: inventory-svc │  │ Span4: payment-svc   │        │  │
  │  │  │ SpanId: ccc          │  │ SpanId: ddd           │        │  │
  │  │  │ ParentSpanId: bbb    │  │ ParentSpanId: bbb     │        │  │
  │  │  │ Kind: CLIENT, 30ms   │  │ Kind: CLIENT, 20ms    │        │  │
  │  │  └──────────────────────┘  └──────────────────────┘        │  │
  │  └────────────────────────────────────────────────────────────┘  │
  └──────────────────────────────────────────────────────────────────┘
概念类比说明
TraceId一次旅行的"行程单号"64 位 / 128 位,贯穿整个请求链路
SpanId行程中的每一段"交通工具"每个操作单元的唯一标识,64 位
ParentSpanId上一段"交通工具"的编号构建 Span 之间的父子调用关系
Trace整张"行程单"由一组 Span 组成的树状结构

2.2 Span 的四种类型(Annotation)

┌──────────────────────────────────────────────────────────────────┐
│                    Span 四种 Annotation                           │
│                                                                  │
│  服务 A (调用方)                      服务 B (被调用方)            │
│  ──────────────                       ──────────────              │
│                                                                  │
│  CS (Client Sent)                    SR (Server Received)        │
│    发送请求  ──────────────────────────→  收到请求               │
│      │                                      │                    │
│      │        网络传输时间 = SR - CS         │                    │
│      │                                      │                    │
│      │                                      │                    │
│      │                                      │                    │
│  CR (Client Received)                 SS (Server Sent)           │
│    收到响应  ←──────────────────────────  返回响应               │
│                                                                  │
│  客户端耗时 = CR - CS                                            │
│  服务端耗时 = SS - SR                                            │
│  网络耗时   = (CR - CS) - (SS - SR)                              │
└──────────────────────────────────────────────────────────────────┘

这四个时间点让 Zipkin 能画出精确的调用时序图,定位到底是服务慢还是网络慢。

2.3 Span 的 Kind 类型

Kind含义谁产生
CLIENT客户端发起调用调用方(如 Feign 发送请求时)
SERVER服务端接收调用被调用方(如 Controller 收到请求时)
PRODUCER消息生产者发送 MQ 消息
CONSUMER消息消费者接收 MQ 消息

三、工作原理

3.1 TraceId 生成与透传

┌──────────────────────────────────────────────────────────────────┐
│                    TraceId 透传全流程                              │
│                                                                  │
│  服务 A (order-service)              服务 B (inventory-service)   │
│  ────────────────                    ────────────────             │
│                                                                  │
│  1. 收到 HTTP 请求                                                │
│     请求头中有 X-B3-TraceId 吗?                                   │
│     ├── 有 → 使用请求头中的 TraceId(透传)                        │
│     └── 没有 → 生成新的 TraceId(入口服务)                         │
│                                                                  │
│  2. Sleuth 生成当前 SpanId                                        │
│     TraceId: abc123(来自请求头)                                  │
│     SpanId: bbb(新生成)                                         │
│     ParentSpanId: aaa(来自请求头的 X-B3-SpanId)                  │
│                                                                  │
│  3. 调用 inventory-service                                       │
│     Feign/RestTemplate 自动拦截                                   │
│     在请求头中注入:                                               │
│       X-B3-TraceId: abc123                                       │
│       X-B3-SpanId: ccc              ← 新 SpanId                  │
│       X-B3-ParentSpanId: bbb        ← 当前 SpanId 作为父 SpanId   │
│       X-B3-Sampled: 1               ← 需要采样                    │
│     ───────────────────────────────→                             │
│                                     4. 收到请求,解析请求头        │
│                                        TraceId: abc123           │
│                                        SpanId: ddd (新生成)       │
│                                        ParentSpanId: ccc         │
│                                                                  │
│                                     5. 处理业务逻辑               │
│                                                                  │
│  6. 收到响应,Sleuth 记录 Span 数据                               │
│     ←───────────────────────────────                             │
│                                                                  │
│  7. 异步上报 Span 到 Zipkin                                       │
└──────────────────────────────────────────────────────────────────┘

关键理解:

  • 入口服务(Gateway 或第一个收到请求的服务):请求头中没有 TraceId,Sleuth 生成新的
  • 中间服务:请求头中有 TraceId,直接使用,保证全链路一个 TraceId
  • 透传载体X-B3-* 系列请求头(B3 是 Zipkin 的传播格式名)
  • 自动透传:Feign、RestTemplate、WebClient、Gateway 都自动支持,无需手动编码

3.2 内部架构

┌──────────────────────────────────────────────────────────────────┐
│                      Sleuth 内部架构                              │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                     Sleuth 核心                             │  │
│  │                                                            │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │  │
│  │  │  Tracer      │  │  SpanHandler │  │  Reporter    │     │  │
│  │  │  (创建Span)   │  │  (处理Span)  │  │  (上报数据)   │     │  │
│  │  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘     │  │
│  │         │                 │                 │              │  │
│  │         ▼                 ▼                 ▼              │  │
│  │  ┌──────────────────────────────────────────────────┐     │  │
│  │  │              Brave (Zipkin 客户端库)               │     │  │
│  │  │  - 生成 TraceId/SpanId                            │     │  │
│  │  │  - 管理 Span 生命周期                             │     │  │
│  │  │  - 采样决策                                       │     │  │
│  │  │  - 上报到 Zipkin Server                           │     │  │
│  │  └──────────────────────────────────────────────────┘     │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                   自动埋点层                                │  │
│  │                                                            │  │
│  │  HTTP 通信    消息队列      数据库      定时任务            │  │
│  │  ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐          │  │
│  │  │Feign   │  │RabbitMQ│  │JDBC    │  │@Scheduled│         │  │
│  │  │RestTpl │  │Kafka   │  │Redis   │  │@Async   │          │  │
│  │  │WebClient│  │Stream  │  │MongoDB │  │         │          │  │
│  │  │Gateway │  │        │  │        │  │         │          │  │
│  │  └────────┘  └────────┘  └────────┘  └────────┘          │  │
│  └────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

Sleuth 底层用的是 Brave(Zipkin 的 Java 客户端库),Sleuth 本身是对 Brave 的 Spring 封装和自动配置。

四、快速上手

4.1 依赖

xml
<!-- Spring Boot 2.x -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

<!-- 上报到 Zipkin -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

4.2 配置

yaml
spring:
  application:
    name: order-service       # 服务名会作为 Span 的 serviceName
  sleuth:
    sampler:
      probability: 1.0        # 采样率:1.0 = 100%,生产建议 0.1~0.3
    web:
      enabled: true           # 自动为 HTTP 请求生成 Span
  zipkin:
    base-url: http://localhost:9411  # Zipkin Server 地址
    sender:
      type: web               # 通过 HTTP 上报(也可以用 rabbit / kafka)

4.3 一条日志看效果

配置 logback 打印 TraceId:

xml
<!-- logback-spring.xml -->
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-},%X{spanId:-}]
                %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

日志输出效果:

2024-01-15 10:30:45.123 [http-nio-8081-1] [abc123,aaa] INFO  Gateway      - 收到请求
2024-01-15 10:30:45.125 [http-nio-8082-1] [abc123,bbb] INFO  OrderService - 开始下单
2024-01-15 10:30:45.155 [http-nio-8083-1] [abc123,ccc] INFO  InventorySvc - 扣减库存
2024-01-15 10:30:45.165 [http-nio-8084-1] [abc123,ddd] INFO  PaymentSvc   - 创建支付单
2024-01-15 10:30:45.185 [http-nio-8082-1] [abc123,bbb] INFO  OrderService - 下单完成

排查问题: 在 ELK 中搜索 TraceId: abc123,这个请求跨 4 个服务的所有日志全部出来,按时间排序就是完整的故事线。

五、采样策略

5.1 采样率配置

yaml
spring:
  sleuth:
    sampler:
      probability: 0.3   # 30% 采样率,生产环境推荐

5.2 自定义采样规则

java
@Configuration
public class SamplerConfig {

    @Bean
    public SamplerFunction<HttpRequest> customSampler() {
        return request -> {
            // 错误请求 100% 采样
            String path = request.path();
            if (path.contains("/error")) {
                return true;
            }

            // 健康检查不采样
            if (path.contains("/actuator")) {
                return false;
            }

            // 其他按概率
            return Math.random() < 0.3;
        };
    }
}

5.3 采样策略选择

策略配置适用场景
全量采样probability: 1.0开发/测试环境
概率采样probability: 0.3生产环境,QPS 高
限速采样rate: 100每秒最多 100 条 Trace
自适应采样自定义错误全采,正常按比例

六、自定义 Span 与 Tag

6.1 手动创建 Span

java
@Service
public class OrderService {

    @Autowired
    private Tracer tracer;

    public Order createOrder(OrderDTO dto) {
        // 创建自定义 Span(包裹一段业务逻辑)
        Span span = tracer.nextSpan()
            .name("createOrder")           // Span 名称
            .tag("orderId", dto.getId())   // 自定义标签
            .tag("amount", String.valueOf(dto.getAmount()))
            .start();

        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            // 业务逻辑... 这段代码的耗时就是 Span 的 Duration
            return doCreateOrder(dto);
        } catch (Exception e) {
            span.error(e);                 // 标记异常
            throw e;
        } finally {
            span.end();                    // 结束 Span
        }
    }
}

6.2 注解方式

java
@RestController
public class OrderController {

    @GetMapping("/order/{id}")
    @NewSpan("getOrder")                              // 创建新 Span
    @SpanTag(key = "orderId", value = "arg[0]")       // 将参数作为标签
    public Order getOrder(@PathVariable Long id) {
        return orderService.getById(id);
    }
}

6.3 给已有 Span 加标签

java
@Autowired
private Tracer tracer;

public void doSomething(String userId) {
    // 给当前 Span 添加标签
    tracer.currentSpan().tag("userId", userId);
    tracer.currentSpan().tag("bizType", "order");
    tracer.currentSpan().event("开始处理订单");  // 添加事件(时间点)
}

七、与各种组件集成

7.1 Feign(自动集成,零配置)

java
// 不需要任何配置,Feign 自动透传 TraceId
@FeignClient(name = "inventory-service")
public interface InventoryClient {
    @GetMapping("/stock/{productId}")
    Inventory getStock(@PathVariable Long productId);
}

原理: Feign 的 RequestInterceptor 自动从当前 Span 读取 TraceId/SpanId,写入请求头 X-B3-*。被调用方 Sleuth 自动解析请求头,恢复链路上下文。

7.2 RestTemplate(自动集成,零配置)

java
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();  // Sleuth 自动注入拦截器
}

7.3 WebClient(自动集成,零配置)

java
@Bean
public WebClient webClient() {
    return WebClient.builder().build();  // Sleuth 自动注入 ExchangeFilterFunction
}

7.4 Gateway(自动集成,零配置)

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: order-route
          uri: lb://order-service
          predicates:
            - Path=/order/**
    # Sleuth 自动在 Gateway 层创建 Span,并透传 TraceId

7.5 RabbitMQ / Kafka

java
// 生产者:Sleuth 自动在消息头中注入 TraceId
@Service
public class OrderProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendOrder(Order order) {
        // Sleuth 自动在消息头加入 X-B3-TraceId 等字段
        rabbitTemplate.convertAndSend("order.exchange", "order.created", order);
    }
}

// 消费者:Sleuth 自动从消息头提取 TraceId,建立新的 Span
@Component
public class OrderConsumer {

    @RabbitListener(queues = "order.created.queue")
    public void handleOrder(Order order) {
        // 当前 Span 的 ParentSpanId 指向生产者的 Span
        log.info("收到订单消息: {}", order.getId());
    }
}

7.6 异步线程

java
@Service
public class OrderService {

    @Autowired
    private Tracer tracer;

    @Async
    public void asyncProcess() {
        // ❌ 异步线程默认丢失 TraceId
        // 日志输出:[ , ] INFO ...  (TraceId 为空)
    }

    @Async
    public void asyncProcessFixed() {
        // ✅ 手动恢复 TraceId
        Span span = tracer.nextSpan().start();
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            // 日志输出:[abc123,xxx] INFO ...  (TraceId 正常)
            doWork();
        } finally {
            span.end();
        }
    }
}

// 更简单的方式:使用 LazyTraceExecutor
@Configuration
public class AsyncConfig {

    @Bean
    public Executor asyncExecutor(Tracer tracer) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);

        // 包装 ThreadPoolExecutor,自动传递 TraceId
        return new LazyTraceExecutor(executor.getThreadPoolExecutor(), tracer);
    }
}

八、与 Zipkin 集成

8.1 Zipkin Server 部署

bash
# Docker 启动 Zipkin
docker run -d -p 9411:9411 openzipkin/zipkin

8.2 上报方式

方式配置适用场景
HTTPsender.type: web开发环境,简单
RabbitMQsender.type: rabbit生产环境,异步、不丢数据
Kafkasender.type: kafka大数据量,高吞吐
yaml
# RabbitMQ 方式上报(推荐生产使用)
spring:
  zipkin:
    sender:
      type: rabbit
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

8.3 Zipkin 控制台

访问 http://localhost:9411,可以:

  1. 按服务名搜索:查询某个服务的所有链路
  2. 按 TraceId 搜索:查询某个请求的完整链路
  3. 查看调用链拓扑:服务之间的依赖关系图
  4. 查看单条链路详情:每个 Span 的耗时、标签、异常信息
  5. 查看耗时分布:P50/P95/P99 延迟
Zipkin 链路详情示例:

    Gateway (2ms)
      └── order-service (80ms)
            ├── inventory-service (30ms)  ← 正常,绿色
            └── payment-service (4.5s)    ← 慢,黄色高亮

    点击 payment-service 可以看到:
      - 请求 URL: POST /payment/create
      - 状态码: 200
      - 耗时: 4.5s
      - 标签: orderId=12345, amount=100.00

九、Sleuth vs Micrometer Tracing

9.1 版本对应关系

Spring BootSpring Cloud链路追踪组件
2.x2021.xSleuth
3.x2022.x+Micrometer Tracing

9.2 核心区别

维度SleuthMicrometer Tracing
底层实现Brave(固定)Brave 或 OpenTelemetry(可选)
API 风格Sleuth 特有 APIMicrometer Observation API(统一)
配置前缀spring.sleuth.*management.tracing.*
依赖spring-cloud-starter-sleuthmicrometer-tracing-bridge-brave
发展方向停止更新持续演进

9.3 从 Sleuth 迁移到 Micrometer Tracing

xml
<!-- 旧:Sleuth -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

<!-- 新:Micrometer Tracing -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-reporter-wavefront</artifactId> <!-- 或 zipkin -->
</dependency>
yaml
# 旧配置
spring:
  sleuth:
    sampler:
      probability: 1.0
  zipkin:
    base-url: http://localhost:9411

# 新配置
management:
  tracing:
    sampling:
      probability: 1.0
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans

代码迁移:

java
// 旧 Sleuth API
@Autowired
private Tracer tracer;
Span span = tracer.nextSpan().start();

// 新 Micrometer Tracing API
@Autowired
private ObservationRegistry registry;
Observation observation = Observation.createNotStarted("mySpan", registry);
observation.start();

核心概念(TraceId、SpanId、透传机制)完全没变,变的是 API 和配置名。 详细迁移指南见 Micrometer Tracing 详解

十、生产最佳实践

  1. 日志中必须包含 TraceId%X{traceId:-} 是排查分布式问题的唯一手段
  2. 采样率合理配置:生产环境 10%-30%,错误请求必须 100% 采样
  3. 异步线程必须传递 TraceId:使用 LazyTraceExecutor 或手动 withSpanInScope
  4. 上报方式选 MQ 而非 HTTP:避免 Span 上报失败影响业务线程
  5. 自定义 Span 要有意义:不要每个方法都创建 Span,只在关键业务节点创建
  6. Span 标签要简洁:不要把整个请求体当标签,标签值不宜超过 256 字符
  7. Zipkin 数据保留策略:通常保留 7 天,不要无限存储

十一、面试要点

Q1:TraceId 和 SpanId 的区别?

  TraceId 是全局链路 ID,一次完整的请求链路中所有服务共享同一个 TraceId。SpanId 是链路中每个操作单元的 ID,每个服务调用产生一个新的 SpanId。通过 ParentSpanId 把 Span 串联成树。

Q2:TraceId 如何跨服务透传?

  通过 HTTP 请求头 X-B3-TraceIdX-B3-SpanIdX-B3-ParentSpanId 透传。Feign 的 RequestInterceptor 自动从当前 Span 读取并写入请求头,被调用方 Sleuth 自动解析请求头恢复上下文。入口服务如果没有请求头,就生成新的 TraceId。

Q3:采样策略有哪些?

  全量采样(probability: 1.0)、概率采样(probability: 0.3)、限速采样(每秒最多 N 条)、自适应采样(错误全采 + 正常按比例)。生产环境推荐概率采样 10%-30%,错误请求 100% 采样。

Q4:异步线程如何保证 TraceId 不丢失?

  使用 LazyTraceExecutor 包装线程池,或手动 tracer.withSpanInScope(span) 包裹异步代码。核心原理是将 TraceId 从当前线程的 MDC 复制到异步线程的 MDC。

Q5:Sleuth 和 Micrometer Tracing 有什么关系?

  Micrometer Tracing 是 Sleuth 的继任者,Spring Boot 3.x 起取代 Sleuth。核心概念(TraceId、SpanId、透传)不变,底层从固定 Brave 变为可选 Brave/OpenTelemetry,API 从 Sleuth 专属变为 Micrometer Observation 统一 API。

Q6:Zipkin、SkyWalking、Jaeger 怎么选?

工具特点适用场景
Zipkin轻量、Spring 生态原生集成中小团队,单一追踪需求
SkyWalking国产、APM 全功能(指标+追踪+日志)国内团队,需要全功能 APM
JaegerCNCF 项目、K8s 原生云原生、K8s 环境

Q7:链路追踪对性能有影响吗?

  有,但通常可忽略。影响主要来自:生成 TraceId/SpanId(纳秒级)、记录 Span 时间戳(纳秒级)、异步上报(后台线程,不影响业务)。采样率 100% 时 QPS 下降约 1%-3%,生产环境 30% 采样影响更小。真正影响大的是 Span 上报的网络 IO,所以推荐用 MQ 异步上报。