Sleuth 分布式追踪入门
Spring Cloud Sleuth 分布式追踪入门
前置知识
在开始本教程之前,建议您具备以下基础知识:
- Spring Boot 基础
- Spring Cloud 基本概念
- 微服务架构设计
- 基本的日志概念
什么是 Spring Cloud Sleuth?
Spring Cloud Sleuth 是 Spring Cloud 的一部分,它为 Spring Cloud 应用程序实现了分布式追踪解决方案。在微服务架构中,一个用户请求通常会经过多个服务,如何跟踪和监控这些跨服务的调用就成为了一个挑战。Sleuth 正是为解决这个问题而生。
Sleuth 主要提供以下功能:
- 分布式追踪:跟踪请求在微服务架构中的流转路径
- 性能分析:分析各个服务调用的耗时,找出性能瓶颈
- 问题定位:当系统出现故障时,快速定位问题所在的服务
- 可视化展示:结合 Zipkin 等工具,可视化展示调用链路
核心概念
Spring Cloud Sleuth 借鉴了 Google Dapper 的设计理念,引入了以下核心概念:
1. Trace(追踪)
Trace 代表了一个完整的请求链路,从请求的发起到最终响应的整个过程。每个 Trace 都有一个唯一的 ID,称为 TraceId。
2. Span(跨度)
Span 是一个工作单元,代表了在一个微服务内部的一次操作。例如,一次 RPC 调用、数据库操作或者缓存访问都可以被视为一个 Span。每个 Span 都有一个唯一的 ID,称为 SpanId。
3. Annotation(注解)
Annotation 用于记录 Span 中的特定事件,Sleuth 中常用的 Annotation 包括:
- cs(Client Sent):客户端发送请求的时间点
- sr(Server Received):服务端接收请求的时间点
- ss(Server Sent):服务端发送响应的时间点
- cr(Client Received):客户端接收响应的时间点
通过这些时间点,可以计算出网络延迟和服务处理时间。
日志格式解析
Sleuth 会在日志中添加追踪信息,格式如下:
[application-name,traceId,spanId,exportable]
- application-name:应用名称
- traceId:追踪 ID,用于标识一个完整的请求链路
- spanId:跨度 ID,用于标识一个工作单元
- exportable:是否将数据导出到 Zipkin 等外部系统(true/false)
例如:
2023-06-15 10:15:32.738 INFO [service-a,5fe8c87b81d7f5d2,9bb58337cc9b1122,true] 12688 --- [nio-8081-exec-1] c.e.demo.controller.ServiceAController : Calling service-b
这条日志表示:
- 应用名称:service-a
- 追踪 ID:5fe8c87b81d7f5d2
- 跨度 ID:9bb58337cc9b1122
- 是否导出:true
环境准备
1. 添加依赖
在您的 pom.xml
中添加 Sleuth 相关依赖:
<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>
</dependencies>
版本兼容性提示
Spring Cloud Sleuth 已在 Spring Boot 3.x 中被 Micrometer Tracing 替代。如果您使用的是 Spring Boot 3.x,请参考 Micrometer Tracing 迁移指南。
2. 配置应用
在 application.yml
中添加 Sleuth 相关配置:
spring:
application:
name: service-a # 应用名称,会显示在追踪日志中
sleuth:
sampler:
probability: 1.0 # 采样率,1.0 表示 100% 采样
logging:
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-},%X{exportable:-}]"
采样率决定了有多少比例的请求会被追踪。在开发环境中,可以设置为 1.0(100%);在生产环境中,可以根据系统负载适当降低采样率。
基本使用
1. 创建一个简单的控制器
@RestController
public class HelloController {
private static final Logger log = LoggerFactory.getLogger(HelloController.class);
@GetMapping("/hello")
public String hello() {
log.info("Hello from Sleuth!");
return "Hello World";
}
}
2. 启动应用并测试
启动应用后,访问 http://localhost:8080/hello
,查看控制台日志:
2023-06-15 10:20:45.123 INFO [service-a,b4a84f11a4d70abc,b4a84f11a4d70abc,true] 12688 --- [nio-8080-exec-1] c.e.demo.controller.HelloController : Hello from Sleuth!
可以看到,Sleuth 自动为请求生成了 TraceId 和 SpanId,并添加到日志中。
跨服务追踪
在微服务架构中,Sleuth 的真正价值在于跨服务追踪。下面我们创建两个服务,演示 Sleuth 如何跟踪跨服务调用。
1. 服务 A
@RestController
public class ServiceAController {
private static final Logger log = LoggerFactory.getLogger(ServiceAController.class);
@Autowired
private RestTemplate restTemplate;
@GetMapping("/start")
public String start() {
log.info("Service A received request");
String response = restTemplate.getForObject("http://localhost:8082/process", String.class);
log.info("Service A received response from Service B: {}", response);
return "Service A => " + response;
}
}
2. 服务 B
@RestController
public class ServiceBController {
private static final Logger log = LoggerFactory.getLogger(ServiceBController.class);
@GetMapping("/process")
public String process() {
log.info("Service B processing request");
return "Processed by Service B";
}
}
3. 配置 RestTemplate
为了让 Sleuth 能够跟踪 RestTemplate 的调用,需要将 RestTemplate 注册为 Bean:
@SpringBootApplication
public class ServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
4. 测试跨服务追踪
启动两个服务(服务 A 端口 8081,服务 B 端口 8082),然后访问 http://localhost:8081/start
,查看两个服务的日志:
服务 A 日志:
2023-06-15 10:30:12.456 INFO [service-a,c7d3e5f8a2b1d9c0,c7d3e5f8a2b1d9c0,true] 12688 --- [nio-8081-exec-1] c.e.s.controller.ServiceAController : Service A received request
2023-06-15 10:30:12.789 INFO [service-a,c7d3e5f8a2b1d9c0,c7d3e5f8a2b1d9c0,true] 12688 --- [nio-8081-exec-1] c.e.s.controller.ServiceAController : Service A received response from Service B: Processed by Service B
服务 B 日志:
2023-06-15 10:30:12.567 INFO [service-b,c7d3e5f8a2b1d9c0,e4f3d2c1b0a9f8e7,true] 12689 --- [nio-8082-exec-1] c.e.s.controller.ServiceBController : Service B processing request
可以看到,两个服务的日志中 TraceId 是相同的(c7d3e5f8a2b1d9c0),但 SpanId 不同。这表明它们属于同一个请求链路,但是不同的工作单元。
自定义跨度
除了自动跟踪 HTTP 请求外,Sleuth 还允许您创建自定义跨度来跟踪特定的操作。
1. 使用 Tracer API
@Service
public class CustomService {
private static final Logger log = LoggerFactory.getLogger(CustomService.class);
@Autowired
private Tracer tracer;
public void doSomethingImportant() {
// 创建一个新的跨度
Span span = tracer.nextSpan().name("custom-operation").start();
try (Tracer.SpanInScope ws = tracer.withSpan(span.start())) {
// 在跨度内执行操作
log.info("Doing something important");
// 模拟耗时操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 添加标签
span.tag("custom.tag", "value");
} finally {
// 结束跨度
span.end();
}
}
}
2. 使用注解
Sleuth 还提供了注解方式来创建自定义跨度:
@Service
public class AnnotatedService {
private static final Logger log = LoggerFactory.getLogger(AnnotatedService.class);
@NewSpan("annotated-operation")
public void doSomethingWithAnnotation(@SpanTag("param") String param) {
log.info("Doing something with annotation and param: {}", param);
// 模拟耗时操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@NewSpan
注解会创建一个新的跨度,@SpanTag
注解会将方法参数添加为跨度的标签。
最佳实践
性能考虑
- 采样率控制:在高流量系统中,适当降低采样率以减少系统开销
- 日志级别:生产环境中使用 INFO 或更高级别的日志,避免过多的 DEBUG 日志
- 异步处理:对于耗时操作,考虑使用异步处理,但需要正确传播 Sleuth 上下文
实施建议
- 命名规范:为自定义跨度使用有意义的名称,便于分析
- 添加标签:为跨度添加有用的标签,如用户 ID、请求参数等
- 集成 Zipkin:结合 Zipkin 使用,获得可视化的追踪数据
- 监控告警:基于追踪数据设置监控告警,及时发现异常
常见问题
1. Sleuth 会对系统性能产生多大影响?
Sleuth 的性能影响主要取决于采样率和系统负载。在默认配置下(10% 采样率),影响通常很小。如果设置为 100% 采样率,可能会增加约 3-5% 的 CPU 和内存开销。
spring:
sleuth:
sampler:
probability: 0.1 # 10% 采样率,生产环境推荐
2. 如何在异步操作中保持追踪上下文?
Sleuth 提供了多种方式来处理异步操作中的上下文传播:
// 使用 TraceRunnable 包装 Runnable
TraceRunnable traceRunnable = new TraceRunnable(tracer, spanNamer, runnable);
// 使用 TraceCallable 包装 Callable
TraceCallable<T> traceCallable = new TraceCallable<>(tracer, spanNamer, callable);
// 对于 CompletableFuture
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 这里的操作会在新的上下文中执行
return "result";
}, new TraceableExecutorService(executorService, tracer));
总结
本教程详细介绍了 Spring Cloud Sleuth 的基本概念和使用方法,包括:
- ✅ 核心概念:Trace、Span 和 Annotation
- ✅ 环境配置:添加依赖和基本配置
- ✅ 基本使用:日志格式和简单示例
- ✅ 跨服务追踪:如何跟踪微服务间的调用
- ✅ 自定义跨度:使用 API 和注解创建自定义跨度
- ✅ 最佳实践:性能考虑和实施建议
希望这个教程对您有所帮助!如果您有任何问题,欢迎在评论区讨论。