SpringCloud--Sleuth 分布式链路追踪

一、Sleuth 概述

Sleuth 是 Spring Cloud 生态系统中的一个组件,它提供了分布式追踪的解决方案。在微服务架构中,一个请求可能会经过多个服务,Spring Cloud Sleuth 可以帮助我们跟踪这些请求,为每个请求生成一个唯一的追踪ID,并在日志中记录这些ID,从而使得我们可以轻松地追踪请求在各个微服务中的流转情况。

二、Sleuth 核心概念

  1. Trace(追踪):一个 Trace 代表一个完整的请求链路,从请求发起一直到最终响应。每个 Trace 都有一个唯一的 Trace ID。
  2. Span(跨度):Span 是 Trace 中的基本工作单元,代表一个服务中的操作。一个 Trace 由多个 Span 组成,每个 Span 都有一个唯一的 Span ID。Span 可以包含如开始时间、结束时间、标签、事件和日志等信息。
  3. Span 的父子关系:在分布式系统中,一个服务可能会调用另一个服务,这时就会创建新的 Span,并且这些 Span 之间会形成父子关系,从而构成一个树状结构。
  4. Annotation:用于记录事件的时间点,例如:
    • cs(Client Sent):客户端发送请求,表示开始一个 Span。
    • sr(Server Received):服务端接收请求,并开始处理。
    • ss(Server Sent):服务端发送响应,表示处理完成。
    • cr(Client Received):客户端接收响应,表示 Span 结束。

三、Sleuth 核心功能

  1. 分布式追踪:跟踪请求在微服务间的流转路径。
  2. Span 管理:记录每个服务处理单元的工作单元。
  3. Trace 上下文传播:在服务间传递追踪上下文。
  4. 与 Zipkin 集成:将追踪数据导出到 Zipkin 进行可视化分析。

四、Sleuth 实现原理

Spring Cloud Sleuth 工作原理主要围绕在分布式系统中追踪请求链路,它通过给请求添加唯一的追踪标识(Trace ID 和 Span ID)并将这些标识在微服务之间传递,从而将整个请求链路串联起来。其实现原理可以总结为:

  1. 追踪与跨度(Trace and Span):
    • Trace:一个 Trace 代表一个完整的请求链路,从请求开始到返回响应,它由一个唯一的 Trace ID 标识。
    • Span:一个 Span 代表一个服务中的一次操作(例如:一个方法调用、一个HTTP请求处理)。每个 Span 有一个唯一的 Span ID,并且会记录操作的开始时间、结束时间、标签(Tags)和日志(Events)。一个 Trace 由多个 Span 组成,这些 Span 通过父子关系形成一棵树状结构。
  2. 标识符的生成与传播:
    • 当一个请求进入系统时,如果请求中没有 Trace ID,Sleuth 会生成一个新的 Trace ID。如果请求中已经存在 Trace ID(例如从上游服务传递过来),则使用该 Trace ID。
    • 同样,每个服务处理请求时,会生成新的 Span(作为子 Span)并分配 Span ID,同时记录父 Span ID(如果没有父 Span,则为根 Span)。
    • Sleuth 通过将 Trace ID 和 Span ID 注入到请求的上下文中,并在服务内部和跨服务调用时传播这些标识符。
  3. 传播机制:
    • Sleuth 支持多种传播机制,例如通过 HTTP 头(默认使用 B3 格式,如 “X-B3-TraceId”, “X-B3-SpanId” 等)在服务间传递追踪信息。
    • 对于消息队列(如 Kafka、RabbitMQ),Sleuth 也会通过消息头来传递追踪信息。
  4. 自动配置与集成:
    • Sleuth 自动与 Spring 应用中的常见组件集成。
    • Sleuth 使用 Spring 的自动配置来设置这些集成,开发者通常只需要添加依赖即可。
  5. 采样(Sampling):
    • 为了减少性能开销,Sleuth 支持采样策略,即只收集一部分请求的追踪信息。默认情况下,Sleuth 使用比例采样,采样率可通过配置调整。
  6. 与追踪后端集成:
    • Sleuth 收集的追踪数据可以通过导出器(exporter)发送到追踪后端,如 Zipkin、OpenTelemetry 等。Sleuth 提供了与 Zipkin 的紧密集成,可以轻松地将追踪数据发送到 Zipkin 服务器进行存储和可视化。
  7. 上下文传播(Context Propagation):
    • Sleuth 使用 ThreadLocal 来存储当前 Span 的上下文,这样在同一个线程中,我们可以通过 Tracer 接口获取当前 Span。对于异步操作,Sleuth 提供了包装器(如 LazyTraceExecutor)来确保上下文在异步线程间正确传递。
  8. 日志集成:
    • Sleuth 会将 Trace ID 和 Span ID 添加到 MDC(Mapped Diagnostic Context)中,这样在日志中就可以输出这些标识符,方便通过日志追踪请求。

五、实现案例(Sleuth + Zipkin)

使用Spring Boot和Spring Cloud Sleuth与Zipkin来实现分布式链路追踪,主要步骤:

  1. 创建两个Spring Boot服务:一个作为服务提供者(provider),一个作为服务消费者(consumer),消费者调用提供者的服务。
  2. 引入Spring Cloud Sleuth和Zipkin的依赖。
  3. 配置Zipkin服务器地址。
  4. 通过RestTemplate或FeignClient进行服务调用,Sleuth会自动追踪。
  5. 启动Zipkin服务器来收集和展示追踪数据。

5.1 环境准备

使用 Docker 启动 Zipkin Server。

# 使用 Docker 运行 Zipkin
docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin

# 或者使用命令行启动(需要 Java 环境)
# curl -sSL https://zipkin.io/quickstart.sh | bash -s
# java -jar zipkin.jar

5.2 添加依赖

在服务提供者和服务消费者服务的pom.xml文件中分别添加Sleuth和Zipkin的依赖。

<!-- Spring Cloud 版本管理 -->
<properties>
    <spring-cloud.version>2022.0.4</spring-cloud.version>
</properties>

<!-- Sleuth + Zipkin 依赖 -->
<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>

5.3 配置文件

在服务提供者和服务消费者服务的application.yml配置文件中配置Zipkin服务器地址。

spring:
  application:
    name: user-service
  sleuth:
    sampler:
      probability: 1.0  # 采样率,1.0表示100%采样
  zipkin:
    base-url: http://localhost:9411  # Zipkin服务器地址
    sender:
      type: web

5.4 服务间调用追踪

当服务都启动成功后,进行服务间的调用时,Sleuth会自动在header中添加Trace信息。

@RestController
@RequestMapping("/users")
public class UserController {
    
    private final RestTemplate restTemplate;
    private final UserService userService;
    
    public UserController(RestTemplate restTemplate, UserService userService) {
        this.restTemplate = restTemplate;
        this.userService = userService;
    }
    
    @GetMapping("/{userId}/orders")
    public UserOrderInfo getUserOrders(@PathVariable Long userId) {
        // Sleuth会自动在header中添加Trace信息
        ResponseEntity<OrderList> orderResponse = restTemplate.exchange(
            "http://order-service/orders/user/" + userId,
            HttpMethod.GET,
            null,
            OrderList.class
        );
        
        User user = userService.getUserById(userId);
        return new UserOrderInfo(user, orderResponse.getBody());
    }
}

5.5 查看 Zipkin 链路追踪

访问 http://localhost:9411 查看链路追踪数据:

  1. 在 Zipkin UI 中可以看到服务间的调用关系。
  2. 查看每个请求的详细链路信息。
  3. 分析服务调用的耗时和依赖关系。

六、高级配置和自定义

6.1 自定义 Span 标签

@Service
public class UserService {
    
    private final Tracer tracer;
    
    public UserService(Tracer tracer) {
        this.tracer = tracer;
    }
    
    public User getUserById(Long userId) {
        // 创建自定义Span
        Span userSpan = tracer.nextSpan().name("db.user.query").start();
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(userSpan)) {
            // 业务逻辑
            userSpan.tag("user.id", userId.toString());
            userSpan.event("query.user.from.database");
            
            // 模拟数据库查询
            return userRepository.findById(userId);
        } catch (Exception e) {
            userSpan.error(e);
            throw e;
        } finally {
            userSpan.end();
        }
    }
}

6.2 异步方法追踪

@Async
@Sleuth
public CompletableFuture<User> getUserAsync(Long userId) {
    // Sleuth会自动传递Trace上下文
    return CompletableFuture.supplyAsync(() -> userRepository.findById(userId));
}

6.3 自定义采样策略

@Configuration
public class TraceConfig {
    
    @Bean
    public Sampler defaultSampler() {
        // 自定义采样策略:只追踪重要接口
        return new Sampler() {
            @Override
            public boolean isSampled(TraceContext traceContext) {
                // 根据请求路径决定是否采样
                String path = getCurrentRequestPath();
                return path.startsWith("/api/") || path.startsWith("/internal/");
            }
        };
    }
}

6.4 数据库调用追踪

@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource dataSource() {
        // 使用P6Spy拦截SQL语句
        P6SpyDataSource dataSource = new P6SpyDataSource(actualDataSource());
        return dataSource;
    }
}

6.5 消息队列追踪

@Component
public class MessageService {
    
    private final Tracer tracer;
    private final StreamBridge streamBridge;
    
    public void sendUserCreatedEvent(User user) {
        // 手动注入Trace信息到消息头
        Message<User> message = MessageBuilder.withPayload(user)
            .setHeader("traceId", tracer.currentSpan().context().traceId())
            .setHeader("spanId", tracer.currentSpan().context().spanId())
            .build();
            
        streamBridge.send("user-created-out-0", message);
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值