链路追踪
在微服务架构中,一次用户请求会跨越多个服务。链路追踪通过在所有服务间传递 TraceId / SpanId,将散落各处的日志串联成完整的调用链,方便定位性能瓶颈和故障。
核心概念
用户请求
│
▼ TraceId: abc123 SpanId: 001
[订单服务]─────────────────────────────────────────
│ 调用 Span 001(根 Span)
▼ TraceId: abc123 SpanId: 002 ParentSpanId: 001
[库存服务]─────────────────────
│ Span 002(子 Span)
▼ TraceId: abc123 SpanId: 003 ParentSpanId: 001
[支付服务]─────────────────────
Span 003(子 Span)
| 概念 | 说明 |
|---|---|
| TraceId | 全局唯一,贯穿整条请求链路 |
| SpanId | 每个服务调用的唯一标识 |
| ParentSpanId | 父 Span,描述调用关系 |
| Span | 一次具体操作(HTTP 调用、DB 查询、MQ 发送)的时间记录 |
| Baggage | 随链路传播的业务数据(如 userId) |
Spring Boot 3.x:Micrometer Tracing
Spring Boot 3.x 用 Micrometer Tracing 替代了 Spring Cloud Sleuth(已归档)。
依赖
// Micrometer Tracing 核心
implementation 'io.micrometer:micrometer-tracing-bridge-brave'
// 上报到 Zipkin
implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
// 若上报到 OTLP / Jaeger,改用:
// implementation 'io.micrometer:micrometer-tracing-bridge-otel'
// implementation 'io.opentelemetry:opentelemetry-exporter-otlp'基础配置
management:
tracing:
sampling:
probability: 1.0 # 采样率:1.0 = 100%(开发),生产建议 0.1~0.01
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans
logging:
pattern:
# 日志中自动插入 traceId 和 spanId
level: "%5p [${spring.application.name},%X{traceId},%X{spanId}]"配置后,每条日志自动带上链路信息:
INFO [order-service,abc123def456,001122aabb] c.e.OrderService - 创建订单 #1001
日志与链路关联(MDC)
Micrometer Tracing 自动将 traceId / spanId 写入 MDC(Mapped Diagnostic Context),Logback / Log4j2 在输出日志时自动读取。
自定义 MDC 字段(如请求来源、用户 ID):
@Component
public class TraceFilter extends OncePerRequestFilter {
private final Tracer tracer;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
// 从请求头提取业务字段写入 MDC
String userId = request.getHeader("X-User-Id");
if (StringUtils.hasText(userId)) {
MDC.put("userId", userId);
}
// 将 traceId 写回响应头,方便前端关联
Span currentSpan = tracer.currentSpan();
if (currentSpan != null) {
response.setHeader("X-Trace-Id",
currentSpan.context().traceId());
}
try {
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}Logback 配置输出自定义 MDC:
<!-- logback-spring.xml -->
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level
[%X{traceId},%X{spanId}] [userId:%X{userId}]
%logger{36} - %msg%n</pattern>日志配置详见 日志。
自定义 Span
在关键业务逻辑上手动创建 Span,精确追踪耗时:
@Service
@RequiredArgsConstructor
public class OrderService {
private final Tracer tracer;
public Order createOrder(CreateOrderRequest req) {
// 方式一:手动管理 Span
Span span = tracer.nextSpan().name("order.create").start();
span.tag("userId", req.getUserId());
span.tag("productCount", String.valueOf(req.getItems().size()));
try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
Order order = doCreate(req);
span.tag("orderId", order.getId().toString());
return order;
} catch (Exception e) {
span.error(e);
throw e;
} finally {
span.end();
}
}
// 方式二:注解(需引入 micrometer-tracing 注解支持)
@NewSpan("inventory.check")
public boolean checkInventory(@SpanTag("productId") Long productId) {
return inventoryRepo.hasStock(productId);
}
}Baggage(跨服务传递业务数据)
Baggage 随 HTTP Header 自动跨服务传播,适合透传 userId、租户 ID 等:
// 写入 Baggage(在入口服务)
@Component
public class UserBaggageFilter extends OncePerRequestFilter {
private final BaggageManager baggageManager;
@Override
protected void doFilterInternal(...) throws ServletException, IOException {
String userId = request.getHeader("X-User-Id");
if (StringUtils.hasText(userId)) {
baggageManager.createBaggage("userId").set(userId);
}
chain.doFilter(request, response);
}
}
// 读取 Baggage(在任意下游服务)
@Service
public class AuditService {
private final BaggageManager baggageManager;
public void audit(String action) {
String userId = baggageManager.getBaggage("userId").get();
log.info("用户 {} 执行了 {}", userId, action);
}
}# 允许传播的 Baggage key(需显式声明)
management:
tracing:
baggage:
remote-fields: userId, tenantId
correlation:
fields: userId, tenantId # 同时写入 MDC异步与线程池中的链路传递
默认情况下,新线程不会继承父线程的 Span 上下文,需使用 Micrometer 提供的包装器:
@Configuration
public class AsyncConfig implements AsyncConfigurer {
private final ObservationRegistry observationRegistry;
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setMaxPoolSize(20);
executor.setThreadNamePrefix("async-");
executor.initialize();
// 包装 Executor,自动传递 Span 上下文
return new ContextPropagatingTaskDecorator(executor);
}
}异步与线程池详见 异步与线程池。
部署 Zipkin
# docker-compose.yml
services:
zipkin:
image: openzipkin/zipkin:latest
ports:
- "9411:9411"访问 http://localhost:9411 查看调用链瀑布图。
生产环境建议 Zipkin + Elasticsearch 持久化存储(默认内存存储会丢失)。
采样策略
management:
tracing:
sampling:
probability: 0.1 # 生产环境采 10%,降低性能开销和存储压力自定义采样(对慢请求或错误请求 100% 采样):
@Bean
public Sampler sampler() {
return (traceContext, sampledFlag) -> {
// 已被标记跳过 → 继续跳过
if (Boolean.FALSE.equals(sampledFlag)) return false;
// 错误请求强制采样
return true;
};
}与监控指标的结合
Micrometer Tracing 与 Micrometer Metrics 共享 ObservationRegistry,一次 @Observed 注解同时产生指标和链路数据:
@Observed(name = "order.create",
contextualName = "creating-order",
lowCardinalityKeyValues = {"type", "standard"})
public Order createOrder(CreateOrderRequest req) {
return doCreate(req);
}指标采集和监控详见 指标采集 和 Actuator监控。