Spring Cloud Sleuth链路追踪案例

wen java案例 1

本文目录导读:

Spring Cloud Sleuth链路追踪案例

  1. 项目架构
  2. 核心依赖配置
  3. 服务A配置和代码
  4. 服务B配置和代码
  5. 服务C配置和代码
  6. 自定义Span示例 (服务B添加)
  7. Docker Compose部署Zipkin
  8. 启动和测试
  9. 完整链路追踪效果

我将为您提供一个完整的Spring Cloud Sleuth链路追踪案例,包含核心配置和实战示例。

项目架构

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  服务A       │────▶│  服务B       │────▶│  服务C       │
│  (前端服务)  │     │  (业务服务)  │     │  (数据服务)  │
└─────────────┘     └─────────────┘     └─────────────┘

核心依赖配置

1 父POM文件 (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>tracing-demo</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.5</spring-cloud.version>
        <spring-cloud-sleuth.version>3.1.8</spring-cloud-sleuth.version>
    </properties>
    <modules>
        <module>service-a</module>
        <module>service-b</module>
        <module>service-c</module>
    </modules>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Cloud Sleuth -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <!-- Zipkin 客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>${spring-cloud-sleuth.version}</version>
        </dependency>
        <!-- Spring Boot Actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

服务A配置和代码

1 服务A配置 (service-a/src/main/resources/application.yml)

server:
  port: 8081
spring:
  application:
    name: service-a
  sleuth:
    sampler:
      probability: 1.0  # 采样率,1.0表示100%采样
    zipkin:
      base-url: http://localhost:9411  # Zipkin服务器地址
  zipkin:
    base-url: http://localhost:9411
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  metrics:
    export:
      zipkin:
        enabled: true
logging:
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss:SSS} [%thread] %-5level %logger{50} - %msg%n"

2 服务A主类 (service-a/src/main/java/com/example/ServiceAApplication.java)

package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class ServiceAApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceAApplication.class, args);
    }
}

3 服务A控制器 (service-a/src/main/java/com/example/controller/TraceController.java)

package com.example.controller;
import com.example.client.ServiceBClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/a")
public class TraceController {
    @Autowired
    private ServiceBClient serviceBClient;
    @Autowired
    private RestTemplate restTemplate;
    /**
     * 使用Feign调用服务B
     */
    @GetMapping("/feign/trace")
    public Map<String, Object> feignTrace() {
        log.info("Service A - Feign调用开始");
        // 调用服务B
        Map<String, Object> result = serviceBClient.callServiceB();
        log.info("Service A - Feign调用结束");
        return buildResponse("service-a", result);
    }
    /**
     * 使用RestTemplate调用服务B
     */
    @GetMapping("/rest/trace")
    public Map<String, Object> restTrace() {
        log.info("Service A - RestTemplate调用开始");
        // 使用RestTemplate调用服务B
        String url = "http://localhost:8082/api/b/process";
        Map<String, Object> result = restTemplate.getForObject(url, Map.class);
        log.info("Service A - RestTemplate调用结束");
        return buildResponse("service-a", result);
    }
    /**
     * 构建统一响应
     */
    private Map<String, Object> buildResponse(String currentService, Map<String, Object> downstreamResult) {
        Map<String, Object> response = new HashMap<>();
        response.put("currentService", currentService);
        response.put("timestamp", System.currentTimeMillis());
        response.put("downstreamResult", downstreamResult);
        return response;
    }
}

4 服务A的Feign客户端 (service-a/src/main/java/com/example/client/ServiceBClient.java)

package com.example.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Map;
@FeignClient(name = "service-b", url = "http://localhost:8082")
public interface ServiceBClient {
    @GetMapping("/api/b/process")
    Map<String, Object> callServiceB();
}

5 服务A配置类 (service-a/src/main/java/com/example/config/RestTemplateConfig.java)

package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

服务B配置和代码

1 服务B配置 (service-b/src/main/resources/application.yml)

server:
  port: 8082
spring:
  application:
    name: service-b
  sleuth:
    sampler:
      probability: 1.0
    zipkin:
      base-url: http://localhost:9411
  zipkin:
    base-url: http://localhost:9411
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

2 服务B控制器 (service-b/src/main/java/com/example/controller/TraceController.java)

package com.example.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.cloud.sleuth.Tracer;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/b")
public class TraceController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private Tracer tracer;
    /**
     * 处理来自服务A的请求
     */
    @GetMapping("/process")
    public Map<String, Object> process() {
        log.info("Service B - 收到请求");
        // 获取当前Span信息
        String traceId = tracer.currentSpan() != null ? 
                         tracer.currentSpan().context().traceId() : "null";
        String spanId = tracer.currentSpan() != null ? 
                        tracer.currentSpan().context().spanId() : "null";
        log.info("Service B - TraceId: {}, SpanId: {}", traceId, spanId);
        // 异步任务测试
        asyncProcess();
        // 调用服务C
        Map<String, Object> result = callServiceC();
        log.info("Service B - 处理完毕");
        return buildResponse(traceId, spanId, result);
    }
    /**
     * 调用服务C
     */
    private Map<String, Object> callServiceC() {
        log.info("Service B - 调用服务C");
        String url = "http://localhost:8083/api/c/process";
        Map<String, Object> result = restTemplate.getForObject(url, Map.class);
        log.info("Service B - 服务C调用完成");
        return result;
    }
    /**
     * 异步处理任务
     */
    private void asyncProcess() {
        new Thread(() -> {
            log.info("Service B - 异步任务开始");
            // 模拟异步处理
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            log.info("Service B - 异步任务结束");
        }).start();
    }
    /**
     * 构建响应
     */
    private Map<String, Object> buildResponse(String traceId, String spanId, 
                                             Map<String, Object> result) {
        Map<String, Object> response = new HashMap<>();
        response.put("currentService", "service-b");
        response.put("traceId", traceId);
        response.put("spanId", spanId);
        response.put("downstreamResult", result);
        return response;
    }
}

服务C配置和代码

1 服务C配置 (service-c/src/main/resources/application.yml)

server:
  port: 8083
spring:
  application:
    name: service-c
  sleuth:
    sampler:
      probability: 1.0
    zipkin:
      base-url: http://localhost:9411
  zipkin:
    base-url: http://localhost:9411
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

2 服务C控制器 (service-c/src/main/java/com/example/controller/TraceController.java)

package com.example.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.cloud.sleuth.Tracer;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/c")
public class TraceController {
    @Autowired
    private Tracer tracer;
    /**
     * 处理来自服务B的请求
     */
    @GetMapping("/process")
    public Map<String, Object> process() {
        log.info("Service C - 开始处理请求");
        // 模拟数据处理
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        // 获取Trace信息
        String traceId = tracer.currentSpan() != null ? 
                         tracer.currentSpan().context().traceId() : "null";
        String spanId = tracer.currentSpan() != null ? 
                        tracer.currentSpan().context().spanId() : "null";
        log.info("Service C - 处理完成, TraceId: {}, SpanId: {}", traceId, spanId);
        Map<String, Object> response = new HashMap<>();
        response.put("currentService", "service-c");
        response.put("traceId", traceId);
        response.put("spanId", spanId);
        response.put("data", "Service C processed data");
        response.put("timestamp", System.currentTimeMillis());
        return response;
    }
}

自定义Span示例 (服务B添加)

package com.example.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class TraceInterceptor {
    @Autowired
    private Tracer tracer;
    /**
     * 创建自定义Span
     */
    public void createCustomSpan() {
        Span customSpan = tracer.nextSpan().name("custom-business-operation").start();
        try (Tracer.SpanInScope ws = tracer.withSpan(customSpan)) {
            log.info("执行自定义业务逻辑");
            // 添加自定义标签
            customSpan.tag("business.type", "order");
            customSpan.tag("business.status", "success");
            // 添加事件
            customSpan.event("business.start");
            // 模拟业务处理
            Thread.sleep(500);
            customSpan.event("business.end");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            customSpan.end();
        }
    }
}

Docker Compose部署Zipkin

version: '3.8'
services:
  zipkin:
    image: openzipkin/zipkin:2.24
    container_name: zipkin
    ports:
      - "9411:9411"
    environment:
      - JAVA_OPTS=-Xmx512m
    networks:
      - tracing-network
  # Elasticsearch用于存储追踪数据(可选)
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - ES_JAVA_OPTS=-Xms256m -Xmx256m
    ports:
      - "9200:9200"
    networks:
      - tracing-network
networks:
  tracing-network:
    driver: bridge

启动和测试

1 启动Zipkin

docker-compose up -d

2 启动服务

# 分别启动服务A、B、C
mvn spring-boot:run -pl service-a
mvn spring-boot:run -pl service-b
mvn spring-boot:run -pl service-c

3 测试接口

# 测试Feign调用链路
curl http://localhost:8081/api/a/feign/trace
# 测试RestTemplate调用链路
curl http://localhost:8081/api/a/rest/trace

4 查看追踪结果

访问Zipkin UI:http://localhost:9411

完整链路追踪效果

当调用成功后,你将在Zipkin UI中看到:

  • 服务A到服务B的完整调用链
  • 每个服务的执行时间
  • Trace ID和Span ID
  • 调用依赖关系图

控制台输出示例:

2024-01-01 10:00:00:123 [http-nio-8081-exec-1] INFO  Service A - Feign调用开始
2024-01-01 10:00:00:124 [http-nio-8081-exec-1] INFO  Service A - 生成TraceId: abc123, SpanId: def456
2024-01-01 10:00:00:126 [http-nio-8081-exec-1] INFO  Service B - 收到请求
2024-01-01 10:00:00:127 [http-nio-8081-exec-1] INFO  Service B - TraceId: abc123, SpanId: ghi789
2024-01-01 10:00:00:500 [http-nio-8081-exec-1] INFO  Service C - 开始处理请求
2024-01-01 10:00:00:1000[http-nio-8081-exec-1] INFO  Service C - 处理完成

这个案例展示了完整的Spring Cloud Sleuth链路追踪实现,包括:

  • 服务间调用追踪
  • 自定义Span
  • 异步任务追踪
  • Zipkin集成
  • 日志关联

抱歉,评论功能暂时关闭!