Sleuth 高级特性
Spring Cloud Sleuth 高级特性
前置知识
在开始本教程之前,建议您具备以下基础知识:
- Spring Cloud Sleuth 基础
- Spring Cloud Sleuth 与 Zipkin 集成
- Spring Boot 异步编程
- Spring Cloud 基本组件
异步处理中的追踪
在微服务架构中,异步处理是提高系统吞吐量的常用方式。但在异步处理中,如何保持追踪上下文的连续性是一个挑战。Spring Cloud Sleuth 提供了多种方式来解决这个问题。
1. @Async 注解
Spring 的 @Async
注解是实现异步处理的简单方式。Sleuth 自动为 @Async
注解的方法提供了追踪支持。
package com.example.demo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncService {
private static final Logger log = LoggerFactory.getLogger(AsyncService.class);
@Async
public void performAsyncTask() {
log.info("Performing async task");
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("Async task completed");
}
}
要启用异步处理,需要在配置类上添加 @EnableAsync
注解:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2. 自定义线程池
如果您使用自定义线程池,需要确保线程池能够传播 Sleuth 的追踪上下文。Sleuth 提供了 LazyTraceExecutor
来包装标准的 Executor
:
package com.example.demo.config;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
public class AsyncConfig {
@Autowired
private BeanFactory beanFactory;
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("async-");
executor.initialize();
// 使用 LazyTraceExecutor 包装标准的 Executor
return new LazyTraceExecutor(beanFactory, executor);
}
}
3. CompletableFuture
对于 CompletableFuture
,Sleuth 提供了 TraceableExecutorService
来包装 ExecutorService
:
package com.example.demo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.instrument.async.TraceableExecutorService;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Service
public class CompletableFutureService {
private static final Logger log = LoggerFactory.getLogger(CompletableFutureService.class);
@Autowired
private BeanFactory beanFactory;
public CompletableFuture<String> performAsyncOperation() {
log.info("Starting async operation");
ExecutorService executorService = Executors.newFixedThreadPool(5);
ExecutorService traceableExecutorService = new TraceableExecutorService(beanFactory, executorService);
return CompletableFuture.supplyAsync(() -> {
log.info("Inside async operation");
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("Async operation completed");
return "Operation result";
}, traceableExecutorService);
}
}
4. 响应式编程
Sleuth 也支持 Spring WebFlux 和 Project Reactor 等响应式编程模型。在响应式编程中,Sleuth 会自动传播追踪上下文。
package com.example.demo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class ReactiveController {
private static final Logger log = LoggerFactory.getLogger(ReactiveController.class);
@GetMapping("/reactive")
public Mono<String> reactiveEndpoint() {
log.info("Handling reactive request");
return Mono.fromSupplier(() -> {
log.info("Inside reactive supplier");
return "Reactive response";
});
}
}
自定义跨度和标签
1. 使用 Tracer API
Sleuth 提供了 Tracer
API,可以用来创建自定义跨度和添加标签:
package com.example.demo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.stereotype.Service;
@Service
public class CustomSpanService {
private static final Logger log = LoggerFactory.getLogger(CustomSpanService.class);
@Autowired
private Tracer tracer;
public void doWorkWithCustomSpan() {
// 创建一个新的跨度
Span customSpan = tracer.nextSpan().name("custom-operation").start();
try (Tracer.SpanInScope ws = tracer.withSpan(customSpan)) {
log.info("Doing work with custom span");
// 添加标签
customSpan.tag("custom.tag1", "value1");
customSpan.tag("custom.tag2", "value2");
// 添加事件(以前称为注解)
customSpan.event("custom-event");
// 模拟耗时操作
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 嵌套跨度
createNestedSpan();
log.info("Work with custom span completed");
} finally {
// 结束跨度
customSpan.end();
}
}
private void createNestedSpan() {
Span nestedSpan = tracer.nextSpan().name("nested-operation").start();
try (Tracer.SpanInScope ws = tracer.withSpan(nestedSpan)) {
log.info("Doing nested operation");
// 模拟耗时操作
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("Nested operation completed");
} finally {
nestedSpan.end();
}
}
}
2. 使用注解
Sleuth 提供了一系列注解,可以用来创建自定义跨度和添加标签:
package com.example.demo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.sleuth.annotation.NewSpan;
import org.springframework.cloud.sleuth.annotation.SpanTag;
import org.springframework.stereotype.Service;
@Service
public class AnnotatedService {
private static final Logger log = LoggerFactory.getLogger(AnnotatedService.class);
@NewSpan("annotated-operation")
public void doWorkWithAnnotation(@SpanTag("param.name") String name, @SpanTag("param.value") int value) {
log.info("Doing work with annotation, name: {}, value: {}", name, value);
// 模拟耗时操作
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用嵌套方法
doNestedWork(name);
log.info("Work with annotation completed");
}
@NewSpan("nested-annotated-operation")
private void doNestedWork(@SpanTag("nested.param") String name) {
log.info("Doing nested work with name: {}", name);
// 模拟耗时操作
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("Nested work completed");
}
}
与 Spring Cloud 组件集成
Sleuth 与其他 Spring Cloud 组件无缝集成,自动为这些组件提供追踪支持。
1. Spring Cloud Gateway
Sleuth 自动为 Spring Cloud Gateway 提供追踪支持,可以跟踪请求从网关到后端服务的完整路径。
# 网关配置示例
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: service-a
uri: http://localhost:8081
predicates:
- Path=/service-a/**
- id: service-b
uri: http://localhost:8082
predicates:
- Path=/service-b/**
sleuth:
sampler:
probability: 1.0
zipkin:
base-url: http://localhost:9411
2. Spring Cloud OpenFeign
Sleuth 自动为 OpenFeign 客户端提供追踪支持,可以跟踪 Feign 客户端的调用。
package com.example.demo.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "service-b", url = "http://localhost:8082")
public interface ServiceBClient {
@GetMapping("/process")
String process(@RequestParam("name") String name);
}
package com.example.demo.controller;
import com.example.demo.client.ServiceBClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FeignController {
private static final Logger log = LoggerFactory.getLogger(FeignController.class);
@Autowired
private ServiceBClient serviceBClient;
@GetMapping("/feign")
public String callWithFeign(@RequestParam(value = "name", defaultValue = "World") String name) {
log.info("Calling Service B with Feign, name: {}", name);
String response = serviceBClient.process(name);
log.info("Received response from Service B: {}", response);
return "Feign => " + response;
}
}
要启用 Feign 客户端,需要在启动类上添加 @EnableFeignClients
注解:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3. Spring Cloud LoadBalancer
Sleuth 自动为 Spring Cloud LoadBalancer 提供追踪支持,可以跟踪负载均衡的请求。
package com.example.demo.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class LoadBalancerConfig {
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
}
package com.example.demo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class LoadBalancerController {
private static final Logger log = LoggerFactory.getLogger(LoadBalancerController.class);
@Autowired
@LoadBalanced
private RestTemplate restTemplate;
@GetMapping("/loadbalancer")
public String callWithLoadBalancer(@RequestParam(value = "name", defaultValue = "World") String name) {
log.info("Calling Service B with LoadBalancer, name: {}", name);
String response = restTemplate.getForObject("http://service-b/process?name=" + name, String.class);
log.info("Received response from Service B: {}", response);
return "LoadBalancer => " + response;
}
}
采样策略
Sleuth 提供了多种采样策略,可以根据需要选择合适的策略。
1. 概率采样
概率采样是最简单的采样策略,根据配置的概率决定是否采样:
spring:
sleuth:
sampler:
probability: 0.1 # 10% 采样率
2. 速率限制采样
速率限制采样可以限制每秒采样的请求数:
spring:
sleuth:
sampler:
rate:
per-second: 100 # 每秒最多采样 100 个请求
buffer-size: 1000 # 缓冲区大小
3. 自定义采样
您可以实现自定义的采样策略:
package com.example.demo.config;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.SamplerFunction;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SamplingConfig {
@Bean
public SamplerFunction<String> myHttpSampler() {
return new SamplerFunction<String>() {
@Override
public Boolean trySample(String request) {
// 对于 /health 和 /info 端点不采样
if (request.contains("/health") || request.contains("/info")) {
return false;
}
// 对于包含 "important" 的请求 100% 采样
if (request.contains("important")) {
return true;
}
// 对于其他请求 10% 采样
return Math.random() < 0.1;
}
};
}
}
最佳实践
性能考虑
- 控制日志量:避免在高流量路径中使用 DEBUG 级别日志
- 选择合适的采样率:根据系统负载和监控需求设置采样率
- 使用异步发送:使用消息队列异步发送追踪数据
- 避免过多自定义跨度:只为关键操作创建自定义跨度
实施建议
- 统一命名规范:为自定义跨度使用统一的命名规范
- 添加有意义的标签:标签应该提供有用的上下文信息
- 监控追踪系统:定期检查追踪系统的健康状况
- 设置告警规则:基于追踪数据设置告警规则
- 定期分析追踪数据:使用追踪数据优化系统性能
常见问题
1. 如何处理敏感信息?
Sleuth 会将追踪数据发送到 Zipkin 等外部系统,因此需要注意不要在跨度中包含敏感信息。可以通过以下方式处理敏感信息:
- 过滤敏感标签:实现
SpanFilter
接口,过滤包含敏感信息的标签 - 屏蔽敏感字段:在记录标签前对敏感字段进行屏蔽
- 使用专用字段:为敏感信息使用专用字段,并在发送前移除
package com.example.demo.config;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SleuthConfig {
@Bean
public SpanFilter sensitiveDataFilter() {
return new SpanFilter() {
@Override
public Span apply(Span span) {
// 移除包含敏感信息的标签
span.removeTag("password");
span.removeTag("creditCard");
// 屏蔽部分标签内容
String email = span.getTag("email");
if (email != null) {
span.tag("email", maskEmail(email));
}
return span;
}
private String maskEmail(String email) {
// 简单的屏蔽实现,实际应用中可能需要更复杂的逻辑
int atIndex = email.indexOf('@');
if (atIndex > 1) {
return email.charAt(0) + "***" + email.substring(atIndex);
}
return email;
}
};
}
}
2. 如何处理大规模系统中的追踪数据?
在大规模系统中,追踪数据量可能非常大,需要考虑以下几点:
- 降低采样率:根据系统规模设置合适的采样率
- 使用分布式存储:使用 Elasticsearch 等分布式存储系统
- 数据聚合:考虑使用数据聚合技术减少存储量
- 设置数据保留期:定期清理旧的追踪数据
- 使用索引:为常用查询创建索引,提高查询性能
# Zipkin 配置示例
spring:
zipkin:
base-url: http://zipkin:9411
sender:
type: kafka # 使用 Kafka 发送数据
sleuth:
sampler:
probability: 0.01 # 1% 采样率,适合大规模系统
总结
本教程详细介绍了 Spring Cloud Sleuth 的高级特性,包括:
- ✅ 异步处理中的追踪:@Async、自定义线程池、CompletableFuture 和响应式编程
- ✅ 自定义跨度和标签:使用 Tracer API 和注解
- ✅ 与 Spring Cloud 组件集成:Gateway、OpenFeign 和 LoadBalancer
- ✅ 采样策略:概率采样、速率限制采样和自定义采样
- ✅ 最佳实践:性能考虑和实施建议
下一步学习
- 学习 Micrometer Tracing 迁移指南
- 探索分布式追踪的其他实现,如 OpenTelemetry、Jaeger 等
- 了解如何将追踪数据与监控系统集成
希望这个教程对您有所帮助!如果您有任何问题,欢迎在评论区讨论。