首页 > 数据库 > 数据库开发技术 > 调用链追踪神器:深入理解MDC






一、引言


在日常开发工作中,排查线上问题时常常会遇到诸如以下困惑:






什么是MDC?




二、快速上手MDC


1. 添加依赖
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.3.0</version></dependency>



2. 配置Logback
<configuration><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [traceId=%X{traceId}] [userId=%X{userId}] %msg%n</pattern></encoder></appender><root level="INFO"><appender-ref ref="CONSOLE" /></root></configuration>



3. 在代码中使用MDC
import org.slf4j.MDC;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class LoggingInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String traceId = generateTraceId();String userId = request.getHeader("User-ID");MDC.put("traceId", traceId);MDC.put("userId", userId);return true;}public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {MDC.remove("traceId");MDC.remove("userId");}private String generateTraceId() {return UUID.randomUUID().toString().replace("-", "");}}




三、深入理解MDC的应用场景


1. 什么是traceId?
traceId 与 spanId 的区别



2. traceId的生成与传递
traceId的生成规则
1.机器标识(8位十六进制):如 1a2b3c4d,代表生成ID的机器。
2.时间戳(13位):如 1701234567890,精确到毫秒。
3.序列号(4位):如 5678,用于同一毫秒内的多次请求。
4.进程ID(5位):如 91011,避免多进程环境下的ID冲突。



3. 实现MDC最佳实践

前端交互中的traceId
示例:在HTTP响应的Header中添加 X-Trace-Id,在Body中也包含 traceId 字段。
{"status": "success","message": null,"traceId": "1a2b3c4d17012345678905678abcd1","data": {}}

服务间调用中的traceId
Feign客户端中添加traceId
import feign.RequestInterceptor;import feign.RequestTemplate;import org.slf4j.MDC;import org.springframework.stereotype.Component;public class FeignTraceInterceptor implements RequestInterceptor {public void apply(RequestTemplate template) {String traceId = MDC.get("traceId");if (traceId != null) {template.header("X-Trace-Id", traceId);}}}
Dubbo服务消费者获取traceId
import com.alibaba.dubbo.common.extension.Activate;import com.alibaba.dubbo.rpc.*;(group = {Constants.CONSUMER}, order = -99999)public class DubboTraceConsumerFilter implements Filter {public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {String traceId = MDC.get("traceId");if (traceId == null) {traceId = UUID.randomUUID().toString().replace("-", "");}RpcContext.getContext().setAttachment("traceId", traceId);return invoker.invoke(invocation);}}
Dubbo服务提供者设置traceId
import com.alibaba.dubbo.common.extension.Activate;import com.alibaba.dubbo.rpc.*;import org.slf4j.MDC;(group = {Constants.PROVIDER}, order = -99999)public class DubboTraceProviderFilter implements Filter {public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {String traceId = invocation.getAttachment("traceId");if (traceId == null) {traceId = UUID.randomUUID().toString().replace("-", "");}MDC.put("traceId", traceId);try {return invoker.invoke(invocation);} finally {MDC.remove("traceId");}}}

消息队列与任务调度中的traceId
RocketMQ消费者消费消息时设置traceId
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;import org.apache.rocketmq.spring.core.MessageListener;import org.slf4j.MDC;import org.springframework.stereotype.Component;(topic = "order-topic", consumerGroup = "order-consumer-group")public class OrderMessageListener implements MessageListener<String> {public void onMessage(String message) {String traceId = MDC.get("traceId");if (traceId == null) {traceId = UUID.randomUUID().toString().replace("-", "");MDC.put("traceId", traceId);}try {// 业务处理} finally {MDC.remove("traceId");}}}
XXL-JOB任务中使用traceId
import com.xxl.job.core.handler.annotation.XxlJob;import org.slf4j.MDC;import org.springframework.stereotype.Component;public class DataSyncJob {("dataSyncHandler")public void dataSyncHandler() throws Exception {String traceId = UUID.randomUUID().toString().replace("-", "");MDC.put("traceId", traceId);try {// 任务处理逻辑} finally {MDC.remove("traceId");}}}

多线程环境中的traceId传递
方式一:使用装饰器模式
import org.slf4j.MDC;import org.springframework.core.task.TaskDecorator;import org.springframework.stereotype.Component;import java.util.Map;public class TraceIdTaskDecorator implements TaskDecorator {public Runnable decorate(Runnable runnable) {Map<String, String> context = MDC.getCopyOfContextMap();return () -> {if (context != null) {MDC.setContextMap(context);}try {runnable.run();} finally {MDC.clear();}};}}
在 AsyncConfig 中应用装饰器:
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.task.TaskDecorator;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;public class AsyncConfig {public Executor asyncExecutor(TraceIdTaskDecorator decorator) {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(20);executor.setQueueCapacity(100);executor.setThreadNamePrefix("AsyncThread-");executor.setTaskDecorator(decorator);executor.initialize();return executor;}}
方式二:使用InheritableThreadLocal
public class TraceIdInheritableThreadLocal {private static final InheritableThreadLocal<String> THREAD_LOCAL = new InheritableThreadLocal<>();public static void set(String traceId) {THREAD_LOCAL.set(traceId);}public static String get() {return THREAD_LOCAL.get();}public static void clear() {THREAD_LOCAL.remove();}}
在主线程中设置 traceId,子线程自动继承:
public class MultiThreadTest {public static void main(String[] args) {TraceIdInheritableThreadLocal.set("trace1234567890");new Thread(() -> {System.out.println("子线程中的traceId: " + TraceIdInheritableThreadLocal.get());}).start();System.out.println("主线程中的traceId: " + TraceIdInheritableThreadLocal.get());}}



输出结果:
主线程中的traceId: trace1234567890子线程中的traceId: trace1234567890




四、实战中的最佳实践



前端与后端交互中的traceId
示例:在HTTP响应的Header中添加 X-Trace-Id,在Body中包含 traceId 字段。
{"status": "success","message": null,"traceId": "2b3c4d5e17022345678906789bcdef2","data": {}}




服务间调用中的traceId传递
Feign客户端中添加traceId
import feign.RequestInterceptor;import feign.RequestTemplate;import org.slf4j.MDC;import org.springframework.stereotype.Component;public class FeignTraceInterceptor implements RequestInterceptor {public void apply(RequestTemplate template) {String traceId = MDC.get("traceId");if (traceId != null) {template.header("X-Trace-Id", traceId);}}}
Dubbo服务消费者获取traceId
import com.alibaba.dubbo.common.extension.Activate;import com.alibaba.dubbo.rpc.*;(group = {Constants.CONSUMER}, order = -99999)public class DubboTraceConsumerFilter implements Filter {public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {String traceId = MDC.get("traceId");if (traceId == null) {traceId = UUID.randomUUID().toString().replace("-", "");}RpcContext.getContext().setAttachment("traceId", traceId);return invoker.invoke(invocation);}}
Dubbo服务提供者设置traceId
import com.alibaba.dubbo.common.extension.Activate;import com.alibaba.dubbo.rpc.*;import org.slf4j.MDC;(group = {Constants.PROVIDER}, order = -99999)public class DubboTraceProviderFilter implements Filter {public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {String traceId = invocation.getAttachment("traceId");if (traceId == null) {traceId = UUID.randomUUID().toString().replace("-", "");}MDC.put("traceId", traceId);try {return invoker.invoke(invocation);} finally {MDC.remove("traceId");}}}




消息队列与任务调度中的traceId传递
RocketMQ消费者消费消息时设置traceId
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;import org.apache.rocketmq.spring.core.MessageListener;import org.slf4j.MDC;import org.springframework.stereotype.Component;(topic = "notification-topic", consumerGroup = "notification-consumer-group")public class NotificationMessageListener implements MessageListener<String> {public void onMessage(String message) {String traceId = MDC.get("traceId");if (traceId == null) {traceId = UUID.randomUUID().toString().replace("-", "");MDC.put("traceId", traceId);}try {// 业务处理逻辑} finally {MDC.remove("traceId");}}}
XXL-JOB任务中使用traceId
import com.xxl.job.core.handler.annotation.XxlJob;import org.slf4j.MDC;import org.springframework.stereotype.Component;public class DataBackupJob {("dataBackupHandler")public void dataBackupHandler() throws Exception {String traceId = UUID.randomUUID().toString().replace("-", "");MDC.put("traceId", traceId);try {// 任务执行逻辑} finally {MDC.remove("traceId");}}}




多线程环境中的traceId传递
使用装饰器模式传递MDC
import org.slf4j.MDC;import org.springframework.core.task.TaskDecorator;import org.springframework.stereotype.Component;import java.util.Map;public class TraceIdTaskDecorator implements TaskDecorator {public Runnable decorate(Runnable runnable) {Map<String, String> contextMap = MDC.getCopyOfContextMap();return () -> {if (contextMap != null) {MDC.setContextMap(contextMap);}try {runnable.run();} finally {MDC.clear();}};}}
在异步配置中应用装饰器:
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;public class AsyncConfig {public Executor asyncExecutor(TraceIdTaskDecorator decorator) {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(50);executor.setQueueCapacity(200);executor.setThreadNamePrefix("AsyncExecutor-");executor.setTaskDecorator(decorator);executor.initialize();return executor;}}
使用InheritableThreadLocal传递MDC
public class TraceIdInheritableThreadLocal {private static final InheritableThreadLocal<String> THREAD_LOCAL = new InheritableThreadLocal<>();public static void set(String traceId) {THREAD_LOCAL.set(traceId);}public static String get() {return THREAD_LOCAL.get();}public static void clear() {THREAD_LOCAL.remove();}}
在主线程中设置 traceId,子线程自动继承:
public class ThreadLocalExample {public static void main(String[] args) {TraceIdInheritableThreadLocal.set("trace9876543210");new Thread(() -> {System.out.println("子线程中的traceId: " + TraceIdInheritableThreadLocal.get());}).start();System.out.println("主线程中的traceId: " + TraceIdInheritableThreadLocal.get());}}



输出结果:
主线程中的traceId: trace9876543210子线程中的traceId: trace9876543210




五、集成日志查看工具


使用SLS查询traceId对应的调用链
接收告警信息:当系统出现异常时,告警信息中包含 traceId。
在SLS中查询:根据 traceId 在SLS中进行日志搜索,快速定位相关的调用链日志,分析问题根源。
示例:




六、总结









- End -
原文作者:IT枫斗者;如有侵权,请联系管理员删除。