实战四:日志链路关联与问题排查演练
服务拆开后,一次用户请求会跨越网关、订单、库存多个进程。排障时最怕“每台机器各查各的日志”。本章实现 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 并建索引。
排查演练:下单失败
场景:用户反馈“下单失败”,接口返回“系统繁忙”,需要定位根因。
- 取 traceId:从网关错误响应头或用户提供的失败时间点,在日志平台按
app=gateway AND status=500检索,拿到这次请求的 traceId。 - 沿链展开:按
traceId=xxx检索全部服务日志,得到事件序列:order-service 记录“调用 stock-service 超时”→ Feign 层重试 3 次 → 熔断打开 → 返回降级。 - 定位根因:切到 stock-service 同 traceId 日志,发现“获取数据库连接等待超过 10s”,进一步看数据库慢 SQL/连接池指标,确认是库存库某条慢 SQL 拖垮连接池。
- 修复与验证:优化慢 SQL、给该调用缩短超时并隔离线程池(防慢调用放大,见常见坑章节);重放压测确认库存服务 p99 恢复,熔断自动闭合。
- 复盘沉淀:把“连接池打满”“熔断打开”的告警阈值写进监控,同类问题下次由告警先行发现。
常见误区
- 只在入口服务打 traceId,下游各打各的 → 必须全链透传。
- traceId 生成在业务线程而不是请求进入处,导致跨异步丢失 → 异步线程要传递上下文(如包装 Runnable/用框架透传)。
- 日志打了两遍(MDC 已加又拼进 message)→ 统一由 pattern 输出。
- 直接生产排障没演练过 → 故障演练时专门练“按 traceId 还原全链路”。
小结
链路关联的本质是“入口生成、出站透传、日志携带、平台聚合”四步闭环。先把 traceId 贯通(MDC 方案理解原理、Micrometer Tracing 用于生产),再把检索能力接到日志平台,排障就能从“翻机器”变成“按 ID 拉时间线”。