外卖霸王餐接口对接中的跨服务上下文传递(Trace ID透传)实现全链路追踪

外卖霸王餐接口对接中的跨服务上下文传递(Trace ID透传)实现全链路追踪

在“吃喝不愁”App的微服务架构中,一次用户参与霸王餐活动的操作会涉及用户服务、活动服务、订单服务、美团API网关等多个系统。若无统一的 Trace ID 透传机制,将难以定位异常链路或性能瓶颈。本文基于 Spring Cloud Sleuth + MDC + 自定义拦截器,实现 Trace ID 在 HTTP、Feign、RestTemplate 及异步线程间的全链路透传。

1. 引入依赖与基础配置

使用 Spring Boot 3.x + Spring Cloud 2022.x:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
    <version>4.0.4</version>
</dependency>
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.4</version>
</dependency>

application.yml 启用 Sleuth 并配置日志输出 Trace ID:

spring:
  application:
    name: bajie-service
  sleuth:
    enabled: true
    propagation:
      type: B3 # 兼容 Zipkin/B3 格式
logging:
  pattern:
    level: "%5p [${spring.application.name},%X{traceId:-},%X{spanId:-}]"

在这里插入图片描述

2. 自定义 Trace 上下文工具类

为兼容非 Web 场景(如定时任务),封装 Trace 工具:

package baodanbao.com.cn.trace;

import org.slf4j.MDC;
import org.springframework.stereotype.Component;

@Component
public class TraceContext {

    private static final String TRACE_ID_KEY = "traceId";

    public void setTraceId(String traceId) {
        MDC.put(TRACE_ID_KEY, traceId);
    }

    public String getTraceId() {
        return MDC.get(TRACE_ID_KEY);
    }

    public void clear() {
        MDC.remove(TRACE_ID_KEY);
    }
}

3. Web 请求入口:拦截并注入 Trace ID

若上游未传递 Trace ID(如前端直接调用),则生成新 ID:

package baodanbao.com.cn.trace.web;

import baodanbao.com.cn.trace.TraceContext;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.UUID;

@Component
public class TraceIdFilter implements Filter {

    @Autowired
    private TraceContext traceContext;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String traceId = httpRequest.getHeader("X-B3-TraceId");
        if (traceId == null || traceId.isEmpty()) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }
        MDC.put("traceId", traceId);
        ((HttpServletResponse) response).setHeader("X-B3-TraceId", traceId);
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

注:Sleuth 默认已处理 B3 头,此处仅为演示自定义逻辑;实际项目可直接依赖 Sleuth 自动注入。

4. Feign 客户端自动透传 Trace ID

Sleuth 默认支持 Feign 拦截,但需确保启用:

package baodanbao.com.cn.meituan.client;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignTraceInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        String traceId = MDC.get("traceId");
        if (traceId != null) {
            template.header("X-B3-TraceId", traceId);
        }
    }
}

Feign 接口示例:

package baodanbao.com.cn.meituan.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@FeignClient(name = "meituan-api", url = "https://openapi.meituan.com")
public interface MeituanFeignClient {

    @PostMapping("/v1/free-meal/apply")
    String applyFreeMeal(@RequestBody String payload);
}

5. RestTemplate 透传 Trace ID

package baodanbao.com.cn.trace.config;

import baodanbao.com.cn.trace.TraceContext;
import org.slf4j.MDC;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;

@Component
public class TraceRestTemplateCustomizer implements RestTemplateCustomizer {

    @Override
    public void customize(RestTemplate restTemplate) {
        restTemplate.getInterceptors().add(new ClientHttpRequestInterceptor() {
            @Override
            public ClientHttpResponse intercept(HttpRequest request, byte[] body,
                                                ClientHttpRequestExecution execution) throws IOException {
                String traceId = MDC.get("traceId");
                if (traceId != null) {
                    request.getHeaders().add("X-B3-TraceId", traceId);
                }
                return execution.execute(request, body);
            }
        });
    }
}

6. 异步线程上下文传递

使用 TaskDecorator 将 MDC 从主线程复制到线程池:

package baodanbao.com.cn.trace.async;

import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class MdcTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
        return () -> {
            if (contextMap != null) {
                MDC.setContextMap(contextMap);
            }
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

配置线程池:

@Bean
public Executor taskExecutor(MdcTaskDecorator decorator) {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setTaskDecorator(decorator);
    executor.setCorePoolSize(4);
    executor.setMaxPoolSize(10);
    executor.setQueueCapacity(100);
    executor.setThreadNamePrefix("bajie-async-");
    executor.initialize();
    return executor;
}

7. 日志输出示例

配置 logback-spring.xml 输出 JSON 日志:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
        <providers>
            <timestamp/>
            <logLevel/>
            <loggerName/>
            <message/>
            <mdc/>
        </providers>
    </encoder>
</appender>

日志片段:

{
  "timestamp": "2025-12-31T09:18:00.123Z",
  "level": "INFO",
  "logger": "baodanbao.com.cn.bajie.service.FreeMealService",
  "message": "User applied for free meal",
  "mdc": { "traceId": "a1b2c3d4e5f67890" }
}

通过统一 traceId,可在 ELK 或 SkyWalking 中串联整个霸王餐参与链路。

本文著作权归吃喝不愁app开发者团队,转载请注明出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值