1. 链路追踪
在微服务架构中,一个请求可能经过多个服务调用,链路追踪(Distributed Tracing)用于记录请求的完整路径,帮助开发者:
排查问题:快速定位性能瓶颈或错误来源。
监控性能:分析各服务调用耗时。
2. Spring Cloud Sleuth
2.1. 关键术语
术语 |
说明 |
Trace |
代表一个完整的请求链路,每个 Trace 有唯一的 Trace ID,贯穿整个请求流程。 |
Span |
代表请求链路中的一个基本工作单元(如服务间的一次调用、数据库操作等)。 |
Span ID |
唯一标识一个 Span,在同一个 Trace 中,Span 之间通过父子关系形成树状结构。 |
Parent ID |
标识当前 Span 的父 Span ID,用于构建 Span 树。 |
Annotation |
用于记录事件时间点(如请求开始、结束、异常等),常见类型包括: |
- cs(Client Send):客户端发起请求,标志 Span 开始。 | |
- sr(Server Receive):服务端接收请求,计算网络延迟(sr - cs)。 | |
- ss(Server Send):服务端完成处理,将结果返回客户端,计算服务处理时间(ss - sr)。 | |
- cr(Client Receive):客户端接收响应,标志 Span 结束。 |
2.2. Sleuth 工作流程
Spring Cloud Sleuth 的工作流程可分为 链路创建、跨服务传递、数据采集、数据输出 四个阶段
2.2.1. 链路创建(请求入口)
当请求进入微服务系统(如网关或服务入口)时,Sleuth 会自动生成 Trace ID 和 Root Span(根 Span,Span ID 通常与 Trace ID 一致)。
traceId:a1b2c3d4e5f6 // 全局唯一
spanId: a1b2c3d4e5f6 // 跟spanId与traceId相同
parentSpanId: null // 无父span
2.2.2. 跨服务传递(核心机制)
- HTTP 调用场景
Sleuth 通过 拦截器(Interceptor) 自动将 Trace ID、Span ID 等元数据注入 HTTP 请求头(默认头名为X-B3-TraceId
、X-B3-SpanId
、X-B3-ParentSpanId
等)。
下游服务接收到请求后,解析请求头中的元数据,创建 子 Span(Parent Span ID 为上游 Span 的 ID)。
// 服务 A 调用服务 B 时的请求头
X-B3-TraceId: a1b2c3d4e5f6 // 全局 Trace ID
X-B3-SpanId: 1234abcd // 服务 A 中当前 Span 的 ID(父 Span ID)
X-B3-ParentSpanId: a1b2c3d4e5f6 // 根 Span ID(仅在非根 Span 中存在)
- 消息中间件场景(如 RabbitMQ、Kafka)
Sleuth 通过 消息监听器(Message Listener) 自动提取 / 注入链路元数据到消息头中,确保跨消息服务的链路连续性。
2.2.3. 输出追踪日志
Sleuth 会自动将 Trace ID 和 Span ID 注入应用日志,方便通过日志关联链路数据,核心实现如下:
- MDC(Mapped Diagnostic Context):
在创建 Span 时,将 traceId 和 spanId 存入 MDC。
日志框架(如 Logback、Log4j)通过配置 %X{traceId}、%X{spanId} 输出这些值。
// 日志格式:
[%d{yyyy-MM-dd HH:mm:ss}]|%-5p|[%X{traceId}][%X{spanId}] %t %C{36} %L %M %m%n</Pattern>
// 输出示例:
[2025-05-23 20:14:38]|INFO |[e6bf514d3401c8d3][182eec2e0d9c59a98b9e91575f71d07d].........
2.3. 底层原理
Spring Cloud Sleuth其底层原理基于 Brave(开源链路追踪库)和 分布式上下文传播机制,通过拦截应用间的通信链路(如 HTTP、消息队列等)自动生成和管理追踪数据(Trace/Span
3. 其他
3.1. MDC
MDC(Mapped Diagnostic Context)是一种基于ThreadLocal,用于在日志记录过程中传递上下文信息的机制。它允许将自定义的键值对与日志记录相关联,并在日志输出时自动将这些键值对添加到日志消息中。
3.1.1. 日志框架集成
// ch.qos.logback.classic.util.LogbackMDCAdapter 类
public class LogbackMDCAdapter implements MDCAdapter {
// 每个线程维护一个 Map,存储 MDC 键值对
private final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<>();
@Override
public void put(String key, String val) {
Map<String, String> map = copyOnThreadLocal.get();
if (map == null) {
map = new HashMap<>();
copyOnThreadLocal.set(map);
}
map.put(key, val);
}
@Override
public String get(String key) {
Map<String, String> map = copyOnThreadLocal.get();
if (map != null && key != null) {
return map.get(key);
} else {
return null;
}
}
// 其他方法...
}
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%X{traceId},%X{spanId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
%X{traceId}:表示从 MDC 中获取 traceId 的值。
%X{spanId}:表示从 MDC 中获取 spanId 的值。
3.1.2. 多线程问题和解决方案
问题:
在异步或多线程场景下,子线程默认无法获取父线程的 MDC 上下文。
解决方案:
手动传递:在创建子线程时,手动将 MDC 上下文复制到子线程。
使用 阿里巴巴TransmittableThreadLocal
:对线程池进行包装,自动复制 MDC 上下文。
集成spring cloud Sleuth:自动实现多线程传递MDC上下文
3.2. Brave
Brave 是由 OpenZipkin 社区开发的 分布式链路追踪库,为微服务架构提供基础的追踪能力。Spring Cloud Sleuth 底层基于 Brave 实现。
3.2.1. 设计理念
- 轻量级与模块化
提供底层 API,不依赖特定框架或平台,可嵌入任何 Java 应用。
- 模块化设计:
核心模块:提供 Trace/Span 管理、传播协议等基础功能。
集成模块:支持 HTTP、gRPC、Kafka、JDBC 等多种通信协议。
存储模块:适配 Zipkin、Jaeger、Elasticsearch 等后端存储。
- 无侵入式设计
通过 拦截器(Interceptor) 和 装饰器(Decorator) 模式集成到现有系统,无需修改核心业务逻辑。
例如:通过 ClientRequestInterceptor 拦截 HTTP 请求,自动注入追踪信息。
3.2.2. 常用协议处理
3.2.2.1. Http协议-客户端
功能:在 HTTP 请求发送前注入追踪头(如 B3 头),并记录请求耗时。
核心类:
TracingClientHttpRequestInterceptor :客户端拦截器接口。
HttpClientHandler:处理客户端请求和响应。
示例:详见代码演示
3.2.2.2. Http协议-服务端
功能:从 HTTP 请求头提取追踪信息,创建服务端 Span。
核心类:
TracingFilter:web过滤器拦截请求
HttpServerHandler:处理服务端请求和响应。
HttpServerParser:自定义解析请求和响应的接口。
示例:详见代码演示
3.2.2.3. Feign集成
功能:通过装饰器模式,封装 Feign 客户端,自动处理追踪信息。
核心类:
LazyTracingFeignClient:懒加载封装Feign客户端
TracingFeignClient:追踪信息处理客户端
示例:详见代码演示