Skip to content

Spring Cloud链路追踪与可观测性

微服务拆分以后,一次请求可能经过很多服务。没有链路追踪时,每个服务都有自己的日志,排查问题只能靠时间、接口和猜测。

本页用于零基础建立概念。继续学习时进入Spring Boot 3可观测性内部原理与生产治理,面试复习进入独立面试题。深度页会从Boot自动配置源码一直推演到Observation生命周期、HTTP与线程传播、OpenTelemetry SDK、OTLP、Collector、采样、失败窗口和生产Runbook。

先区分新旧技术线

技术线Java与Spring基线常见实现应该怎样学习
极老存量线JDK 7、Boot 1.x或非Boot应用项目锁定的旧追踪库或手工日志关联先隔离维护并制定升级计划,不能使用Boot 2/3 API
存量线JDK 8、Spring Boot 2.7.x、Spring Cloud 2021.xSpring Cloud Sleuth 3.x、Brave、Zipkin理解维护方式、B3传播和迁移风险,不把老配置直接复制到Boot 3
现代线Java 17+、Spring Boot 3.xMicrometer Observation、Micrometer Tracing、OpenTelemetry或Brave Bridge新项目主线,重点掌握自动配置、标准传播、异步上下文、OTLP和Collector

JDK 7/8仍然是存量系统的重要基础,但Boot 2.7要求Java 8,Spring Boot 3要求Java 17起步。知识库不会让读者永远停留在旧技术,也不会用Java 17 API假装可以直接运行在JDK 7/8上:原理共用,依赖、配置、代码语法和迁移步骤分别讲解。

链路追踪解决的是:

一次请求经过哪些服务、每段耗时多少、在哪个服务失败、日志如何串起来。

阅读入口:先看上下文,再看三类信号

按“Trace Context 创建 → Header 传播 → Span 生命周期 → Logs/Metrics 关联 → Collector 查询”阅读。先保证链路串得起来,再讨论采样率、指标基数和存储成本。

核心原理:上下文传播驱动三类信号关联

入口创建或接收 Trace Context,Observation 在网关、HTTP 客户端、Controller 和数据库等边界创建 Span。上下文通过 W3C Trace Context 或 B3 Header 传播,日志写入 MDC,指标从同一 Observation 记录时延和错误,后端据此还原拓扑。

mermaid
flowchart TD
    A["Gateway接收请求"] --> B["创建trace/span上下文"]
    B --> C["Header传播到Feign/WebClient"]
    C --> D["下游服务创建子Span"]
    D --> E["Observation记录耗时与异常"]
    E --> F["Logs写入traceId"]
    E --> G["Metrics聚合P95/错误率"]
    F --> H["Collector/后端关联Trace"]
    G --> H

Trace 是请求级采样数据,Metrics 是聚合数据,Logs 是事件细节;不能用高基数用户 ID 充当指标标签,也不能只看单一信号判断根因。

为什么需要链路追踪

假设一次下单请求经过:

text
Gateway -> order-service -> user-service -> coupon-service -> payment-service

用户只看到“下单失败”,但研发要回答:

  • 请求有没有进入网关?
  • 网关有没有路由到订单服务?
  • 订单服务调用用户服务是否超时?
  • 优惠券服务是否失败?
  • 支付服务返回了什么错误?
  • 哪个服务耗时最长?

没有 traceId,排查会非常困难。

核心概念

概念说明
Trace一次完整请求链路
TraceId一次请求的唯一 ID
Span链路中的一个操作,例如一次 HTTP 调用、一次 SQL
SpanId某个操作的唯一 ID
ParentSpanId父操作 ID,用来组成调用树
Baggage跨服务传播的业务上下文
MDC日志上下文,让日志自动打印 traceId
mermaid
flowchart TD
    A["TraceId: T100"] --> B["Span 1 Gateway"]
    B --> C["Span 2 order-service"]
    C --> D["Span 3 user-service"]
    D --> E["Span 4 payment-service"]

这里用纵向顺序调用保证小屏可读。Trace是整条链,Span是链上的一段;如果订单服务并行调用用户和支付服务,两个下游Span也可以拥有同一个父Span,但应在复杂原理页拆成更小的图说明,避免一张宽图在移动端被截断。

TraceId 如何跨服务传播

链路追踪的关键是把 traceId 放到请求头里,跨服务透传。

mermaid
flowchart TD
    A["Gateway 生成 TraceId"] --> B["请求头写入 TraceId"]
    B --> C["order-service 读取并记录"]
    C --> D["Feign 调用时继续放入请求头"]
    D --> E["user-service 读取同一个 TraceId"]

常见请求头标准:

  • traceparent:W3C Trace Context 标准。
  • b3X-B3-TraceId:Zipkin B3 传播格式。
  • 自定义 X-Trace-Id:很多老项目使用。

新项目更推荐使用标准传播格式,方便接入 OpenTelemetry、Zipkin、Tempo 等系统。

日志为什么要打印 traceId

如果日志里没有 traceId,你只能这样查:

text
2026-07-02 10:00:01 order-service 下单失败
2026-07-02 10:00:01 user-service 查询用户
2026-07-02 10:00:02 payment-service 支付失败

你无法确定这些日志是否属于同一次请求。

有 traceId 后:

text
[traceId=T100] order-service 下单失败
[traceId=T100] user-service 查询用户
[traceId=T100] payment-service 支付失败

排查范围立刻缩小。

Sleuth、Micrometer Tracing、OpenTelemetry、SkyWalking 对比

技术时代/定位说明
Spring Cloud Sleuth老 Spring Cloud 常见自动给日志和请求加 traceId/spanId,常配 Zipkin
Micrometer Tracing新 Spring Boot/Spring Cloud 常见Spring Boot 3 以后更主流,统一观测门面
OpenTelemetry开放标准跨语言、跨平台采集 traces、metrics、logs
Zipkin链路追踪后端存储和展示调用链
SkyWalkingAPM 平台通过探针采集链路、指标、拓扑和告警
Jaeger / Tempo链路追踪后端云原生场景常见

为什么从 Sleuth 走向 Micrometer Tracing

Sleuth 时代重点是“让 Spring Cloud 应用自动带 traceId”。后来的观测体系更强调统一:

  • Metrics:指标。
  • Traces:链路。
  • Logs:日志。

Micrometer 本来就是 Spring 生态常用指标门面,Micrometer Tracing 让链路追踪也融入同一套观测体系。OpenTelemetry 则是更大的跨语言标准。

一次链路采集流程

mermaid
flowchart TD
    A["应用收到请求"] --> B["创建或读取 Trace Context"]
    B --> C["创建当前 Span"]
    C --> D["执行业务逻辑"]
    D --> E["调用下游时注入 Trace Header"]
    E --> F["记录耗时、状态、异常"]
    F --> G["导出 Span 到追踪后端"]
    G --> H["Zipkin / SkyWalking / Tempo 展示调用链"]

链路追踪不是只加一个 ID。它还会记录:

  • 服务名。
  • 接口名。
  • 开始时间。
  • 结束时间。
  • 耗时。
  • 状态码。
  • 异常信息。
  • 调用关系。

RT、P95和P99怎么看

RT是Response Time,表示一次请求的响应耗时。P95/P99是百分位延迟,表示把一段时间内的请求耗时从小到大排序后,95%或99%位置上的耗时。

例如某接口P99=800ms,表示约99%的请求耗时不超过800ms,剩下约1%的请求比800ms更慢。它不是平均值,也不是最大值。

排查微服务接口慢时,不能只看平均RT。平均值会掩盖少量长尾请求,而真实用户体验经常被P95/P99影响。更完整的解释、计算示例和排查场景见:RT、P95和P99到底是什么

存量系统手工透传traceId

下面代码只用于“暂时无法接入完整追踪框架”的存量兜底,用来说明Header与MDC的基本关系。它不是Boot 3生产推荐方案,也不具备Span父子关系、标准采样、Scope、Exporter和Collector数据链。Boot 3项目应使用框架管理的Micrometer Tracing与标准Propagator,完整实现见Receiver与Sender调用链

java
@Configuration
public class FeignTraceConfig {

    @Bean
    public RequestInterceptor traceRequestInterceptor() {
        return template -> {
            String traceId = TraceContext.getTraceId();
            if (traceId != null) {
                template.header("X-Trace-Id", traceId);
            }
        };
    }
}

下游服务收到请求后,把 traceId 放入日志 MDC:

java
public class TraceFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String traceId = httpRequest.getHeader("X-Trace-Id");
        if (traceId == null || traceId.trim().isEmpty()) {
            traceId = UUID.randomUUID().toString();
        }
        MDC.put("traceId", traceId);
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.remove("traceId");
        }
    }
}

日志格式中加入:

text
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId}] %logger - %msg%n

存量Gateway手工生成traceId

Gateway是外部流量入口,下面仍是未接完整Tracing时的兜底示意。接入Boot 3观测框架后,不要再用手写UUID覆盖框架创建或提取的Trace Context,否则日志ID与真实Span树可能不一致。

java
@Component
public class GatewayTraceFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String traceId = exchange.getRequest().getHeaders().getFirst("X-Trace-Id");
        if (traceId == null || traceId.trim().isEmpty()) {
            traceId = UUID.randomUUID().toString();
        }
        final String finalTraceId = traceId;
        ServerWebExchange newExchange = exchange.mutate()
                .request(builder -> builder.header("X-Trace-Id", finalTraceId))
                .build();
        return chain.filter(newExchange);
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

生产中如果接入 Micrometer Tracing 或 OpenTelemetry,很多传播动作会由框架自动完成,但你仍然要理解底层就是“上下文创建、请求头传播、日志关联、后端展示”。

可观测性三件套

类型解决什么问题例子
Metrics 指标系统整体是否健康QPS、RT、CPU、错误率、线程池
Traces 链路一次请求经过哪里Gateway 到订单服务再到支付服务
Logs 日志某个点发生了什么异常堆栈、业务参数、错误码

三者要结合使用:

  • 指标告诉你“系统变慢了”。
  • 链路告诉你“慢在哪个服务”。
  • 日志告诉你“为什么慢或为什么失败”。

常见问题

traceId 断了

常见原因:

  • Feign 没有透传请求头。
  • 异步线程没有复制上下文。
  • MQ 消息没有携带 traceId。
  • Gateway 生成了新 traceId 但没有向下游传。
  • 日志 MDC 没有正确清理或设置。

链路平台没有数据

排查:

  • 应用是否引入 tracing 依赖。
  • 采样率是否过低。
  • exporter 地址是否正确。
  • 网络是否能访问 Zipkin/SkyWalking/Collector。
  • 服务名是否配置正确。

日志里 traceId 错乱

通常是线程复用时 MDC 没清理。线程池中的线程会复用,如果请求结束不 remove,下一个请求可能打印上一个请求的 traceId。

生产建议

建议原因
所有入口生成或接收 traceId保证请求从入口可追踪
Feign、Gateway、MQ 都要传播上下文防止链路断裂
日志格式必须包含 traceId方便按链路检索日志
异步线程要传递上下文防止线程切换后丢失 traceId
控制采样率高流量系统全量链路成本高
关键错误链路可强制采样方便排查异常请求

和服务调用链路的关系

服务调用链路 解决“请求怎么走”;链路追踪解决“请求走过哪里、哪里慢、哪里失败”。

没有链路追踪,微服务调用链路一旦变长,系统就会变成黑盒。

小结

链路追踪的核心不是某个具体框架,而是 trace、span、上下文传播和日志关联。老项目常见 Sleuth + Zipkin,新项目更常见 Micrometer Tracing、OpenTelemetry 或 SkyWalking。学习时要掌握原理:入口创建 trace,上下游传播 trace,日志和链路平台按 trace 聚合。