Spring Cloud 链路追踪完全指南
一、为什么需要链路追踪?
1.1 微服务架构的痛点
在单体应用时代,如果一个请求出错了,我们只需要查看一个应用的日志就能定位问题。但在微服务架构中,一个用户请求可能会经过多个服务:
用户请求 → 网关 → 订单服务 → 库存服务 → 支付服务 → 物流服务
问题来了: 如果这个请求失败了,你怎么知道是哪个服务出的问题?每个服务都有自己的日志,如何把这些日志关联起来?
1.2 链路追踪的价值
链路追踪(Distributed Tracing)就是为了解决这个问题而诞生的:
- 问题定位:快速找到请求在哪个服务、哪个环节出错了
- 性能分析:找出慢查询、性能瓶颈
- 依赖梳理:清晰看到服务之间的调用关系
- 故障排查:异常发生时,快速定位根因
💡 面试重点:链路追踪的核心价值是什么?
- 核心价值:全链路可观测性。让开发人员能够"看到"请求在分布式系统中的完整执行路径。
二、链路追踪核心概念
2.1 Trace(追踪)
一条完整的请求链路,从请求进入系统到返回响应的整个过程。
举例:用户下单购买商品的完整流程就是一个Trace。
2.2 Span(跨度)
Trace中的一个个工作单元,代表系统中的一个单独的工作步骤。
类比理解:
- Trace 就像一张完整的快递物流单
- Span 就像物流单上的每个节点(揽收、运输、派送)
// 一个完整的Trace包含多个Span
Trace: 用户下单
├── Span 1: 创建订单
├── Span 2: 扣减库存
├── Span 3: 调用支付
└── Span 4: 发送通知
2.3 Trace ID 和 Span ID
每个Trace都有唯一的 Trace ID,每个Span都有唯一的 Span ID。通过这些ID,我们可以将分散在不同服务日志中的请求信息关联起来。
日志示例:
[Trace ID=abc123, Span ID=def456] 订单服务:创建订单成功
[Trace ID=abc123, Span ID=ghi789] 库存服务:扣减库存成功
[Trace ID=abc123, Span ID=jkl012] 支付服务:支付超时
看到相同的Trace ID,我们就知道这些日志属于同一个请求。
2.4 Parent Span ID
Span之间可以存在父子关系,通过Parent Span ID来表示。
Span A (创建订单)
├── Span B (查询商品) - Parent Span ID = A的ID
├── Span C (查询用户) - Parent Span ID = A的ID
└── Span D (扣减库存) - Parent Span ID = A的ID
💡 面试重点:Trace ID、Span ID、Parent Span ID三者关系是什么?
- Trace ID:全局唯一,标识一条完整的请求链路
- Span ID:全局唯一,标识链路中的单个工作单元
- Parent Span ID:指向父级Span的ID,用于构建调用树
三、Spring Cloud链路追踪演进史
3.1 第一代:Spring Cloud Sleuth
时代背景:Spring Cloud早期版本
核心功能:
- 自动为请求生成Trace ID和Span ID
- 将追踪信息注入到日志中
- 支持与Zipkin、SkyWalking等系统集成
使用方式:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
# application.yml
spring:
application:
name: order-service
sleuth:
zipkin:
base-url: http://localhost:9411 # Zipkin服务器地址
sampler:
probability: 1.0 # 采样率,1.0表示100%采集
3.2 问题与挑战
Spring Cloud Sleuth存在一些问题:
- 维护停滞:Sleuth项目已经停止更新
- 标准化不足:与OpenTelemetry等标准不兼容
- 功能受限:缺乏一些高级特性
3.3 第二代:Micrometer Tracing
时代背景:Spring Boot 3.x + Spring Cloud 2022.x
核心改进:
- 基于Micrometer观测框架
- 兼容OpenTelemetry标准
- 更好的性能和扩展性
- 与Micrometer Prometheus监控集成
使用方式:
<!-- pom.xml -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation-processor</artifactId>
</dependency>
# application.yml
management:
tracing:
sampling:
probability: 1.0 # 采样率
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
💡 面试重点:为什么从Sleuth迁移到Micrometer?
- Sleuth已停止维护,Micrometer是官方推荐方案
- Micrometer支持OpenTelemetry标准,更符合行业趋势
- 与Spring Boot 3.x的Actuator监控深度集成
四、Micrometer Tracing实战
4.1 项目结构
假设我们有一个微服务项目:
microservice-demo/
├── gateway-service/ # 网关服务
├── order-service/ # 订单服务
├── inventory-service/ # 库存服务
└── payment-service/ # 支付服务
4.2 添加依赖
每个服务都需要添加:
<dependencies>
<!-- Micrometer Tracing核心 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!-- Zipkin报告器 -->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<!-- Spring Boot Actuator(用于监控端点) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
4.3 配置文件
# application.yml
server:
port: 8081
spring:
application:
name: order-service
# 链路追踪配置
management:
tracing:
sampling:
probability: 1.0 # 生产环境建议0.1(10%采样)
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
# 暴露端点用于查看监控信息
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
4.4 业务代码示例
订单服务:
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private InventoryService inventoryService;
@PostMapping
public String createOrder(@RequestBody OrderRequest request) {
// 这里会自动生成Trace ID和Span ID
log.info("创建订单,商品ID: {}", request.getProductId());
// 调用库存服务(会自动传递Trace信息)
inventoryService.deductStock(request.getProductId(), request.getQuantity());
// 调用支付服务
String paymentResult = restTemplate.postForObject(
"http://payment-service/payments",
request,
String.class
);
return "订单创建成功";
}
}
库存服务:
@Service
public class InventoryService {
@Autowired
private InventoryRepository inventoryRepository;
public void deductStock(Long productId, Integer quantity) {
// 会继承订单服务的Trace ID,生成新的Span ID
log.info("扣减库存,商品ID: {}, 数量: {}", productId, quantity);
Inventory inventory = inventoryRepository.findByProductId(productId);
if (inventory.getStock() < quantity) {
throw new RuntimeException("库存不足");
}
inventory.setStock(inventory.getStock() - quantity);
inventoryRepository.save(inventory);
log.info("库存扣减成功");
}
}
4.5 日志输出
配置日志格式,显示Trace信息:
# logback-spring.xml
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [TraceId=%X{traceId}, SpanId=%X{spanId}] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
日志输出示例:
2025-12-07 10:30:15 [http-nio-8081-exec-1] INFO [TraceId=abc123, SpanId=def456] OrderController - 创建订单,商品ID: 1001
2025-12-07 10:30:15 [http-nio-8081-exec-1] INFO [TraceId=abc123, SpanId=ghi789] InventoryService - 扣减库存,商品ID: 1001, 数量: 2
2025-12-07 10:30:15 [http-nio-8081-exec-1] INFO [TraceId=abc123, SpanId=ghi789] InventoryService - 库存扣减成功
2025-12-07 10:30:16 [http-nio-8081-exec-1] INFO [TraceId=abc123, SpanId=jkl012] PaymentService - 支付处理中...
💡 关键点:注意到所有日志都有相同的TraceId(abc123),但有不同的SpanId。这样我们就能轻松追踪整个请求链路。
五、Zipkin可视化平台
5.1 Zipkin简介
Zipkin是Twitter开源的分布式链路追踪系统,用于收集、存储和可视化链路追踪数据。
5.2 安装Zipkin
方式一:Docker安装(推荐)
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.3 访问Zipkin UI
启动后访问:http://localhost:9411/zipkin/
5.4 查询链路
按时间查询:
- 选择时间范围(如"最近1小时")
- 点击"Run Query"查询
- 点击具体的Trace查看详情
按服务查询:
- 在"Service Name"下拉框中选择特定服务(如order-service)
- 可以快速定位该服务的调用链路
按标签查询:
- 支持按自定义标签过滤(如"http.method=POST")
5.5 链路详情分析
点击某个Trace后,会看到类似这样的界面:
总耗时: 250ms
├── Gateway Service: 250ms
│ ├── Order Service: 200ms
│ │ ├── 查询订单: 20ms
│ │ ├── 扣减库存: 80ms
│ │ │ └── 数据库查询: 70ms
│ │ └── 调用支付: 100ms
│ │ └── 支付网关: 95ms
│ └── 响应处理: 50ms
从可视化图中可以看出:
- 整个链路耗时250ms
- 扣减库存环节最慢(80ms)
- 数据库查询是性能瓶颈(70ms)
六、高级特性
6.1 自定义Span
默认情况下,框架会自动为HTTP请求、数据库查询等创建Span。但有时我们需要手动创建Span来追踪特定业务逻辑。
方式一:使用NewSpan注解
@Service
public class OrderService {
@NewSpan("calculate-order-price")
public BigDecimal calculatePrice(Long orderId) {
// 这段代码会被包装在一个独立的Span中
log.info("计算订单价格");
// 复杂的计算逻辑
BigDecimal price = doComplexCalculation();
return price;
}
}
方式二:编程式创建
@Service
public class OrderService {
@Autowired
private Tracer tracer;
public void processOrder(Order order) {
// 创建自定义Span
Span span = tracer.nextSpan()
.name("process-order")
.tag("order.id", order.getId().toString())
.tag("order.type", order.getType());
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
// 业务逻辑
log.info("处理订单: {}", order.getId());
doProcess(order);
} finally {
span.end(); // 结束Span
}
}
}
6.2 添加标签和注解
添加标签(Tag):
Span span = tracer.nextSpan().name("database-query");
// 添加标签
span.tag("db.type", "mysql");
span.tag("db.statement", "SELECT * FROM orders WHERE id = ?");
span.tag("db.table", "orders");
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
// 执行数据库查询
List<Order> orders = orderRepository.findAll();
} finally {
span.end();
}
添加注解(Annotation):
// 记录时间戳事件
span.event("query-start");
// ... 执行查询
span.event("query-complete");
6.3 跨服务传递Trace信息
默认情况下,使用RestTemplate、Feign、WebClient等HTTP客户端会自动传递Trace信息。但如果你使用自定义HTTP客户端,需要手动传递。
示例:使用HttpClient手动传递
public class CustomHttpClient {
public void sendRequest(String url, String traceId, String spanId) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("X-B3-TraceId", traceId) // B3协议标准
.header("X-B3-SpanId", spanId)
.header("X-B3-Sampled", "1")
.build();
client.send(request, HttpResponse.BodyHandlers.ofString());
}
}
💡 面试重点:B3协议是什么?
- B3是Zipkin提出的一种链路追踪信息传递协议
- 通过HTTP Header传递Trace ID和Span ID
- 主要Header包括:X-B3-TraceId、X-B3-SpanId、X-B3-ParentSpanId、X-B3-Sampled
6.4 异步场景下的Trace传递
在异步场景(如线程池、@Async)中,Trace信息不会自动传递到子线程。
问题示例:
@Async
public void asyncProcess(Order order) {
// 这里会丢失父线程的Trace信息
log.info("异步处理订单: {}", order.getId());
}
解决方案:
@Service
public class OrderService {
@Autowired
private Tracer tracer;
@Async
public void asyncProcess(Order order) {
// 手动传递Trace信息
Span parentSpan = tracer.currentSpan();
Span childSpan = tracer.nextSpan()
.name("async-process")
.tag("order.id", order.getId());
try (Tracer.SpanInScope ws = tracer.withSpanInScope(childSpan)) {
// 现在可以正确记录Trace信息
log.info("异步处理订单: {}", order.getId());
doProcess(order);
} finally {
childSpan.end();
}
}
}
七、生产环境最佳实践
7.1 采样率配置
生产环境建议不要100%采集链路数据,否则会产生大量数据。
management:
tracing:
sampling:
probability: 0.1 # 10%采样率
采样策略:
- 开发环境:1.0(100%采集,方便调试)
- 测试环境:0.5(50%采集)
- 生产环境:0.1(10%采集)或更低
动态采样:
@Configuration
public class TracingConfig {
@Bean
public Sampler sampler() {
// 根据服务名动态调整采样率
return name -> {
if (name.startsWith("critical-")) {
return 1.0; // 关键服务100%采样
} else {
return 0.1; // 普通服务10%采样
}
};
}
}
7.2 敏感信息脱敏
链路追踪数据中可能包含敏感信息(如用户ID、手机号),需要进行脱敏处理。
@Component
public class SensitiveDataFilter implements SpanHandler {
@Override
public boolean end(TraceContext context, MutableSpan span, Cause cause) {
// 过滤敏感标签
Map<String, String> tags = span.tags();
tags.entrySet().removeIf(entry ->
entry.getKey().contains("password") ||
entry.getKey().contains("token") ||
entry.getKey().contains("phone")
);
// 脱敏处理
if (tags.containsKey("user.id")) {
String userId = tags.get("user.id");
tags.put("user.id", maskUserId(userId));
}
return true;
}
private String maskUserId(String userId) {
return userId.substring(0, 3) + "****" + userId.substring(userId.length() - 4);
}
}
7.3 性能优化
批量上报:
management:
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
batching:
enabled: true # 启用批量上报
message-size: 10000 # 批量大小
异步上报:
management:
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
sender:
type: kafka # 使用Kafka异步发送
7.4 告警配置
集成告警系统,在链路异常时及时通知。
@Component
public class TracingAlertHandler {
@Autowired
private AlertService alertService;
@EventListener
public void handleSlowTrace(SlowTraceEvent event) {
if (event.getDuration() > 3000) { // 超过3秒
alertService.sendAlert("慢链路告警",
String.format("TraceId: %s, 耗时: %dms",
event.getTraceId(),
event.getDuration())
);
}
}
}
八、常见面试题
8.1 基础概念题
Q1:什么是分布式链路追踪?
A:分布式链路追踪是一种用于追踪和监控分布式系统中请求流转路径的技术。它通过为每个请求生成唯一的Trace ID,并为请求经过的每个服务生成Span ID,将分散在不同服务中的日志和监控数据关联起来,形成一个完整的调用链路。
核心价值:
- 快速定位故障点
- 分析性能瓶颈
- 理解服务依赖关系
Q2:Trace ID、Span ID、Parent Span ID有什么区别?
A:
- Trace ID:全局唯一标识符,标识一条完整的请求链路。一条Trace中的所有Span共享同一个Trace ID。
- Span ID:全局唯一标识符,标识链路中的单个工作单元。每个Span都有自己的Span ID。
- Parent Span ID:指向父级Span的ID,用于构建调用树。表示当前Span是由哪个Span调用的。
举例:
Trace ID: abc123(整个下单流程)
├── Span ID: def456(创建订单)
├── Span ID: ghi789(扣减库存,Parent Span ID=def456)
└── Span ID: jkl012(调用支付,Parent Span ID=def456)
Q3:Spring Cloud Sleuth和Micrometer Tracing有什么区别?
A:
| 对比项 | Spring Cloud Sleuth | Micrometer Tracing |
|---|---|---|
| 状态 | 已停止维护 | 官方推荐,活跃维护 |
| Spring Boot支持 | 2.x | 3.x |
| 标准兼容 | 自定义协议 | OpenTelemetry标准 |
| 集成度 | 独立组件 | 与Actuator深度集成 |
| 功能完整性 | 基础功能 | 更丰富的高级特性 |
迁移建议:新项目直接使用Micrometer Tracing,旧项目逐步迁移。
8.2 实战应用题
Q4:在微服务架构中,如何实现跨服务的链路追踪?
A:实现跨服务链路追踪的关键是Trace信息的传递。
实现方式:
-
HTTP Header传递:使用B3协议或W3C标准,通过HTTP Header传递Trace ID和Span ID
// 自动传递的Header
X-B3-TraceId: abc123
X-B3-SpanId: def456
X-B3-ParentSpanId: ghi789
X-B3-Sampled: 1 -
使用支持的HTTP客户端:
- RestTemplate:自动传递
- Feign:自动传递
- WebClient:自动传递
- HttpClient:需要手动传递
-
消息队列场景:
@KafkaListener(topics = "order-events")
public void handleOrderEvent(OrderEvent event,
@Header("X-B3-TraceId") String traceId,
@Header("X-B3-SpanId") String spanId) {
// 从消息Header中提取Trace信息
// 并在当前线程中继续传递
}
Q5:如何处理异步场景下的链路追踪?
A:异步场景(线程池、@Async、消息队列)中Trace信息不会自动传递,需要手动处理。
解决方案:
-
@Async场景:
@Async
public void asyncProcess(Order order) {
Span parentSpan = tracer.currentSpan();
Span childSpan = tracer.nextSpan()
.name("async-process")
.tag("order.id", order.getId());
try (Tracer.SpanInScope ws = tracer.withSpanInScope(childSpan)) {
log.info("异步处理订单: {}", order.getId());
} finally {
childSpan.end();
}
} -
线程池场景:
ExecutorService executor = new ThreadPoolExecutor(...);
// 在提交任务前捕获当前Span
Span parentSpan = tracer.currentSpan();
executor.submit(() -> {
Span childSpan = tracer.nextSpan()
.name("task-in-pool")
.setParent(parentSpan.context());
try (Tracer.SpanInScope ws = tracer.withSpanInScope(childSpan)) {
// 执行任务
} finally {
childSpan.end();
}
}); -
使用装饰器(推荐):
ExecutorService executor = new TtlExecutors.getTtlExecutor(
new TraceableExecutorService(tracer, executorService)
);
Q6:生产环境如何配置链路追踪采样率?
A:生产环境建议根据业务特点配置合适的采样率。
采样策略:
-
固定采样率:
management:
tracing:
sampling:
probability: 0.1 # 10%采样 -
动态采样:
@Bean
public Sampler sampler() {
return name -> {
// 关键服务100%采样
if (name.contains("payment")) {
return 1.0;
}
// 普通服务10%采样
return 0.1;
};
} -
基于规则的采样:
@Bean
public Sampler sampler() {
return SamplingRule.builder()
.putRule("critical-service", 1.0)
.putRule("normal-service", 0.1)
.putRule("background-job", 0.01)
.build();
}
建议:
- 开发环境:100%采样
- 测试环境:50%采样
- 生产环境:10%采样,关键服务100%
Q7:链路追踪数据量太大怎么办?
A:生产环境中,链路追踪数据量可能非常庞大,需要采取以下措施:
-
降低采样率:
management:
tracing:
sampling:
probability: 0.05 # 5%采样 -
数据过期策略:
zipkin:
storage:
type: elasticsearch
elasticsearch:
index: zipkin
index-replica: 0
index-shards: 3
ttl: 7d # 数据保留7天 -
使用时序数据库:
- Zipkin + Elasticsearch:适合大规模存储
- Zipkin + Cassandra:高可用场景
- 使用ClickHouse:超大规模场景
-
异步批量上报:
management:
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
batching:
enabled: true
message-size: 50000
timeout: 5s
8.3 高级应用题
Q8:如何在链路追踪中添加自定义标签?
A:添加自定义标签可以帮助更好地理解和过滤链路数据。
方式一:使用注解
@NewSpan("process-order")
public void processOrder(@SpanTag("order.id") Long orderId) {
// order.id会自动成为Span的标签
}
方式二:编程式添加
public void processOrder(Order order) {
Span span = tracer.nextSpan()
.name("process-order")
.tag("order.id", order.getId().toString())
.tag("order.type", order.getType())
.tag("order.amount", order.getAmount().toString());
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
// 业务逻辑
} finally {
span.end();
}
}
方式三:拦截器统一添加
@Component
public class TracingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
Span span = tracer.currentSpan();
if (span != null) {
span.tag("http.method", request.getMethod());
span.tag("http.uri", request.getRequestURI());
span.tag("user.id", getCurrentUserId());
}
return true;
}
}
Q9:如何排查慢链路?
A:慢链路排查是链路追踪的重要应用场景。
排查步骤:
-
在Zipkin中查找慢链路:
- 按"持续时间"排序
- 筛选耗时超过阈值的Trace
-
分析调用链路:
总耗时: 2000ms
├── 网关层: 50ms
├── 订单服务: 1950ms
│ ├── 查询订单: 50ms
│ ├── 库存检查: 1800ms ⚠️ 性能瓶颈
│ │ └── 数据库查询: 1750ms
│ └── 创建订单: 100ms
└── 响应处理: 50ms -
定位慢SQL:
- 查看数据库查询Span
- 检查是否有全表扫描
- 分析索引使用情况
-
优化建议:
// 优化前:N+1查询问题
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
User user = userRepository.findById(order.getUserId()); // N次查询
}
// 优化后:使用JOIN查询
List<Order> orders = orderRepository.findAllWithUsers(); // 1次查询
Q10:如何监控第三方服务的调用?
A:监控第三方服务调用(如支付网关、短信服务)非常重要。
实现方式:
-
自定义HTTP客户端包装:
@Component
public class TracedHttpClient {
@Autowired
private Tracer tracer;
public String sendHttpRequest(String url, String body) {
Span span = tracer.nextSpan()
.name("http-client-request")
.tag("http.url", url)
.tag("http.method", "POST")
.tag("service.type", "third-party");
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
long startTime = System.currentTimeMillis();
// 发送HTTP请求
String response = httpClient.post(url, body);
long duration = System.currentTimeMillis() - startTime;
span.tag("http.status_code", "200");
span.tag("http.duration", String.valueOf(duration));
return response;
} catch (Exception e) {
span.tag("error", e.getMessage());
throw e;
} finally {
span.end();
}
}
} -
使用RestTemplate拦截器:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(Tracer tracer) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(
new TracingClientInterceptor(tracer)
));
return restTemplate;
}
}
public class TracingClientInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) {
Span span = tracer.nextSpan()
.name("http-client")
.tag("http.url", request.getURI().toString())
.tag("http.method", request.getMethod().name());
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
ClientHttpResponse response = execution.execute(request, body);
span.tag("http.status", String.valueOf response.getStatusCode()));
return response;
} catch (Exception e) {
span.tag("error", e.getMessage());
throw e;
} finally {
span.end();
}
}
}
九、实战案例:电商下单链路追踪
9.1 场景描述
用户下单购买商品,涉及多个服务:
- 网关服务:路由转发
- 订单服务:创建订单
- 库存服务:扣减库存
- 支付服务:处理支付
- 通知服务:发送通知
9.2 完整链路
用户请求
↓
Gateway Service (Trace ID: abc123, Span ID: 1a2b3c)
↓
Order Service (Trace ID: abc123, Span ID: 4d5e6f, Parent Span ID: 1a2b3c)
├→ 查询商品信息 (Span ID: 7g8h9i)
├→ 查询用户信息 (Span ID: 0j1k2l)
└→ 创建订单 (Span ID: 3m4n5o)
↓
Inventory Service (Trace ID: abc123, Span ID: 6p7q8r, Parent Span ID: 3m4n5o)
└→ 扣减库存 (Span ID: 9s0t1u)
↓
Payment Service (Trace ID: abc123, Span ID: 2v3w4x, Parent Span ID: 3m4n5o)
└→ 调用支付网关 (Span ID: 5y6z7a)
↓
Notification Service (Trace ID: abc123, Span ID: 8b9c0d, Parent Span ID: 3m4n5o)
└→ 发送短信 (Span ID: 1e2f3g)
9.3 代码实现
订单服务:
@Service
public class OrderService {
@Autowired
private InventoryClient inventoryClient;
@Autowired
private PaymentClient paymentClient;
@Autowired
private NotificationClient notificationClient;
@Autowired
private Tracer tracer;
@NewSpan("create-order")
public Order createOrder(OrderRequest request) {
Span span = tracer.currentSpan();
span.tag("order.user_id", request.getUserId().toString());
span.tag("order.product_id", request.getProductId().toString());
span.tag("order.amount", request.getAmount().toString());
log.info("开始创建订单");
// 1. 查询商品信息
Product product = productClient.getProduct(request.getProductId());
// 2. 查询用户信息
User user = userClient.getUser(request.getUserId());
// 3. 创建订单
Order order = buildOrder(request, product, user);
orderRepository.save(order);
span.tag("order.id", order.getId().toString());
// 4. 扣减库存
try {
inventoryClient.deductStock(request.getProductId(),
request.getQuantity());
} catch (Exception e) {
span.tag("inventory.error", e.getMessage());
throw new OrderException("库存扣减失败", e);
}
// 5. 调用支付
try {
PaymentResult paymentResult = paymentClient.pay(
order.getId(),
order.getAmount()
);
span.tag("payment.id", paymentResult.getPaymentId());
} catch (Exception e) {
span.tag("payment.error", e.getMessage());
// 支付失败,回滚库存
inventoryClient.rollbackStock(request.getProductId(),
request.getQuantity());
throw new OrderException("支付失败", e);
}
// 6. 发送通知
notificationClient.sendOrderNotification(order.getId());
log.info("订单创建成功: {}", order.getId());
return order;
}
}
9.4 Zipkin可视化效果
总览图:
- 总耗时:1.5秒
- 涉及服务:5个
- Span数量:12个
详细信息:
Gateway: 1500ms
├── Filter: 10ms
├── Routing: 20ms
└── Order Service: 1470ms
├── 查询商品: 50ms
├── 查询用户: 80ms
├── 创建订单: 100ms
├── 扣减库存: 400ms ⚠️ 需要优化
│ └── 数据库操作: 380ms
├── 调用支付: 600ms
│ ├── 支付网关: 550ms
│ └── 回调处理: 50ms
└── 发送通知: 200ms
└── 短信网关: 180ms
性能优化建议:
- 库存服务数据库查询慢(380ms),需要添加索引或使用缓存
- 支付网关耗时较长(550ms),考虑使用异步回调
- 通知服务可以考虑使用消息队列异步处理
十、总结与建议
10.1 核心要点
-
链路追踪是微服务可观测性的三大支柱之一
- Logs(日志):记录发生了什么
- Metrics(指标):量化系统状态
- Traces(链路):理解请求路径
-
Micrometer Tracing是未来方向
- Spring Boot 3.x官方推荐
- 兼容OpenTelemetry标准
- 与监控深度集成
-
生产环境注意事项
- 合理配置采样率
- 注意敏感信息脱敏
- 定期清理历史数据
- 建立告警机制
10.2 学习路径
初学者:
- 理解Trace、Span等基本概念
- 在本地搭建Zipkin环境
- 实现一个简单的微服务调用链路追踪
进阶开发者:
- 学习自定义Span和标签
- 掌握异步场景的Trace传递
- 了解OpenTelemetry标准
架构师:
- 设计企业级链路追踪方案
- 集成多种可观测性工具
- 建立完整的监控告警体系
10.3 参考资源
官方文档:
- Spring Boot Tracing: https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.observability.tracing
- Micrometer: https://micrometer.io/docs/tracing
- Zipkin: https://zipkin.io/pages/
开源项目:
- Spring Cloud Sleuth: https://github.com/spring-cloud/spring-cloud-sleuth
- Micrometer Tracing: https://github.com/micrometer-metrics/tracing
- OpenTelemetry: https://opentelemetry.io/
推荐阅读:
- 《分布式系统原理与范型》
- 《微服务架构设计模式》
- 《可观测性工程》
💡 最后建议:链路追踪不仅仅是技术实现,更是一种工程思维。在设计和开发微服务时,要始终思考"如何让系统更可观测",这样才能在出现问题时快速定位和解决。