实战四:日志链路关联与问题排查演练

服务拆开后,一次用户请求会跨越网关、订单、库存多个进程。排障时最怕“每台机器各查各的日志”。本章实现 traceId 在调用链上的透传与日志关联,并演练一个典型故障的排查过程。说明:Spring Cloud Sleuth 已停止演进,Spring Boot 3.x 官方推荐 Micrometer Tracing(可对接 OpenTelemetry/Zipkin),本章用轻量 MDC + 过滤器方案讲透原理,生产可平滑替换为 Micrometer Tracing。

目标:一个 traceId 贯穿全链

用户请求 → 网关(traceId=t1) → order-service(t1) → stock-service(t1)

规则:入口没有 traceId 就生成一个,有则透传;每个服务把当前 traceId 放进日志上下文(MDC),logback 把它打进每行日志;跨服务调用时把 traceId 放进请求头继续传递。

网关/入口:生成 traceId

@Component
public class TraceIdFilter 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.isBlank()) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }
        exchange.getRequest().mutate()
                .header("X-Trace-Id", traceId).build();
        // 放入响应头,方便客户端/排查者回传
        exchange.getResponse().getHeaders().set("X-Trace-Id", traceId);
        MDC.put("traceId", traceId);
        return chain.filter(exchange).contextWrite(ctx -> ctx.put("traceId", traceId))
                .doFinally(s -> MDC.remove("traceId"));
    }

    @Override
    public int getOrder() { return -200; } // 最先执行
}

服务端:Feign 透传 + 日志输出

每个下游服务同样用拦截器从请求头取出 traceId 放入 MDC(思路同上)。关键还有“出站透传”:order-service 调 stock-service 时把 traceId 带给对方。

// Feign 请求拦截器:出站请求自动携带 traceId
@Component
public class TraceIdFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        String traceId = MDC.get("traceId");
        if (traceId != null) {
            template.header("X-Trace-Id", traceId);
        }
    }
}
<!-- logback-spring.xml:pattern 里带上 traceId -->
<property name="LOG_PATTERN"
          value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} traceId=%X{traceId:-} - %msg%n"/>

MDC 方案要求“入口设置、出口清理”,否则线程池复用会把上个请求的 traceId 串到下一个请求(典型 Bug:日志串号)。用 Micrometer Tracing 时由框架自动完成注入与清理,并额外产生 spanId/耗时等结构化数据。

日志采集侧

各服务日志进入聚合平台后(见日志聚合章节),按 traceId 检索即可把一次请求的所有日志按时间拼成一条完整时间线:网关鉴权耗时 → 订单落库 → Feign 调库存 → 库存扣减返回。日志平台索引字段要包含 traceId 并建索引。

排查演练:下单失败

场景:用户反馈“下单失败”,接口返回“系统繁忙”,需要定位根因。

  1. 取 traceId:从网关错误响应头或用户提供的失败时间点,在日志平台按 app=gateway AND status=500 检索,拿到这次请求的 traceId。
  2. 沿链展开:按 traceId=xxx 检索全部服务日志,得到事件序列:order-service 记录“调用 stock-service 超时”→ Feign 层重试 3 次 → 熔断打开 → 返回降级。
  3. 定位根因:切到 stock-service 同 traceId 日志,发现“获取数据库连接等待超过 10s”,进一步看数据库慢 SQL/连接池指标,确认是库存库某条慢 SQL 拖垮连接池。
  4. 修复与验证:优化慢 SQL、给该调用缩短超时并隔离线程池(防慢调用放大,见常见坑章节);重放压测确认库存服务 p99 恢复,熔断自动闭合。
  5. 复盘沉淀:把“连接池打满”“熔断打开”的告警阈值写进监控,同类问题下次由告警先行发现。

常见误区

  • 只在入口服务打 traceId,下游各打各的 → 必须全链透传。
  • traceId 生成在业务线程而不是请求进入处,导致跨异步丢失 → 异步线程要传递上下文(如包装 Runnable/用框架透传)。
  • 日志打了两遍(MDC 已加又拼进 message)→ 统一由 pattern 输出。
  • 直接生产排障没演练过 → 故障演练时专门练“按 traceId 还原全链路”。

小结

链路关联的本质是“入口生成、出站透传、日志携带、平台聚合”四步闭环。先把 traceId 贯通(MDC 方案理解原理、Micrometer Tracing 用于生产),再把检索能力接到日志平台,排障就能从“翻机器”变成“按 ID 拉时间线”。

笔记加载中…