外卖霸王餐接口对接中的跨服务上下文传递(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开发者团队,转载请注明出处!
实现全链路追踪&spm=1001.2101.3001.5002&articleId=156446487&d=1&t=3&u=39ff3a061aba4e6f8b7ae4d6258ad883)
241

被折叠的 条评论
为什么被折叠?



