📝 Part 4:分布式链路追踪 —— Sleuth + Zipkin 实践
在微服务架构中,一个请求可能会经过多个服务节点。为了准确地监控调用链、定位性能瓶颈和排查问题,分布式链路追踪(Distributed Tracing) 是必不可少的能力。
Spring Cloud 提供了对 Sleuth + Zipkin 的开箱即用支持,帮助开发者轻松实现全链路追踪。本文将带你了解 Sleuth 和 Zipkin 的工作原理,并结合实际项目演示如何配置和使用它们。
一、什么是 Sleuth?
Spring Cloud Sleuth 是 Spring Cloud 提供的一个分布式请求链路追踪组件。它可以在不修改业务逻辑的前提下,自动为每个请求生成唯一的 traceId
和 spanId
,并注入到日志、消息、RPC 调用等上下文中。
核心概念:
概念 | 说明 |
---|---|
traceId | 唯一标识一次完整的请求链路,贯穿整个调用树 |
spanId | 表示调用链中的一个节点(即一次服务调用或操作) |
exportable | 是否将 span 数据发送给 Zipkin 等收集系统 |
二、什么是 Zipkin?
Zipkin 是 Twitter 开源的一款分布式追踪系统,用于收集 Sleuth 产生的 trace 数据,并提供可视化界面展示调用链、耗时分析、服务依赖等信息。
你可以将其理解为 Sleuth 的“数据接收器 + 可视化平台”。
三、Sleuth 如何与 MDC、ThreadLocal 协作?
Sleuth 在底层通过 MDC
注入 traceId
和 spanId
,从而让日志框架能够识别这些字段。这使得我们在日志中可以清晰地看到每次请求的调用路径。
示例日志输出:
2025-07-03 17:00:00.000 [http-nio-8080-exec-1] INFO c.e.demo.service.UserService - Processing user request
[applicationName,9e6b50dc63abea4c,9e6b50dc63abea4c] true
其中:
applicationName
是服务名;9e6b50dc63abea4c
是traceId
;- 第二个值是
spanId
。
四、快速接入 Sleuth + Zipkin
1. 添加依赖(Maven)
<!-- 引入 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>
</dependency>
2. 配置 application.yml
spring:
application:
name: user-service
zipkin:
base-url: http://localhost:9411
sender:
type: web
sleuth:
sampler:
probability: 1.0 # 采样率,1.0 表示全部采集
✅ 注意:
zipkin.base-url
应指向你部署的 Zipkin Server 地址。
五、启动 Zipkin Server
你可以通过 Docker 快速启动 Zipkin:
docker run -d -p 9411:9411 openzipkin/zipkin
访问 http://localhost:9411 即可打开 Zipkin UI。
六、多服务调用链演示
假设我们有三个服务:
gateway-service
(网关)user-service
(用户服务)order-service
(订单服务)
当 gateway-service
调用 user-service
,再由 user-service
调用 order-service
时,Sleuth 会自动生成如下结构:
Trace ID: abcdefghijklmnop
└── Span 1: gateway-service -> user-service
└── Span 2: user-service -> order-service
Zipkin UI 会展示完整的调用链、各服务耗时、错误信息等。
七、异步任务中的上下文传递
Sleuth 内部已经集成对异步任务的支持,例如:
@Async
CompletableFuture
ScheduledExecutorService
只需引入正确的依赖(如 spring-context-support
),Sleuth 就能自动传播 traceId
到子线程中。
如果你使用的是自定义线程池,推荐使用 TtlExecutors
包装线程池以确保上下文正确传递:
ExecutorService executor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
八、日志格式配置(logback.xml)
为了让日志中显示 traceId
和 spanId
,你需要配置日志模板:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg [traceId=%X{traceId}, spanId=%X{spanId}]%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
这样每条日志都会带上当前的 traceId
和 spanId
,便于排查问题。
九、Dubbo 微服务中 Sleuth 的兼容性
在 Dubbo 微服务中,Sleuth 默认只支持基于 HTTP 的调用,因此需要额外配置才能实现 Dubbo 协议下的上下文透传。
解决方案:
- 手动注入 RpcContext
String traceId = tracer.currentSpan().context().traceIdString();
RpcContext.getContext().setAttachment("X-B3-TraceId", traceId);
- 使用 Dubbo Filter 自动注入上下文
@Activate(group = {Constants.PROVIDER, Constants.CONSUMER})
public class TraceFilter implements Filter {
@Autowired
private Tracer tracer;
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String traceId = tracer.currentSpan().context().traceIdString();
RpcContext.getContext().setAttachment("X-B3-TraceId", traceId);
return invoker.invoke(invocation);
}
}
- 使用 Apache SkyWalking 替代方案(更高级)
如果 Dubbo 服务较多,建议直接使用 SkyWalking 或 Jaeger 等 APM 工具进行全链路追踪。
十、总结建议
组件 | 推荐配置 |
---|---|
日志追踪 | 使用 Sleuth 自动生成 traceId/spanId |
日志格式 | 结合 MDC 打印 traceId、spanId |
多服务调用 | Sleuth + Zipkin 实现全链路追踪 |
异步任务 | 使用 TTL 或 Sleuth 自带的线程池封装 |
Dubbo 微服务 | 自定义 Filter 注入上下文,或使用 SkyWalking |
📌 参考链接