本文目录导读:

我将为您提供一个完整的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集成
- 日志关联