首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >从原理到实践:MDC日志链路追踪指南

从原理到实践:MDC日志链路追踪指南

作者头像
叔牙
发布2025-07-29 10:59:27
发布2025-07-29 10:59:27
43800
代码可运行
举报
运行总次数:0
代码可运行

一、背景描述

对于很多基建不完备的中小型企业,很多时候业务服务都是单点模式,或者简单的分布式或者集群,除了存在数据孤岛问题,还存在严重的线上问题跟踪困难症结。举两个简单例子:

  • 订单问题定位: 通过订单号筛选服务日志,然后人工定位与订单号相匹配的日志信息,依赖大脑形成信息上下文,然后反推代码和问题
  • 用户问题定位: 通过用户id查询相关的日志,然后从日志中筛选出与当前操作相关的有效信息,大脑组装调用链路,并分析哪个环节出现了问题,再反向追踪和分析代码,然后解决问题

在一些小规模、低复杂度的服务体系中,通过上述介绍的日志搜索查看和分析方式,没有太大问题,也能够很快的分析出请求调用链和问题代码,但是仔细思考后,发现分析日志和调用链路的过程中会存在以下几个明显的问题:

  • 不精确: 在缺少有效业务标识或者统一业务标识存在并发操作的情况下,筛选出来的日志信息并不精确,以订单号为例,如果同一订单号的请求是并发的,那么以订单号为基线筛选出来的日志信息可能来自于多个请求,后续的分析难度增加。
  • 调用链路不清晰,速度慢: 对于请求缺少唯一标识或者并发请求场景,应用内部的方法调用可能没有明确的追踪标识(如Trace ID),导致无法快速串联多个方法调用的链路,并发请求产生的多段日志时间序列是穿插的,严重拖慢了分析日志和定位问题的节奏。
  • 上下文信息不足,效率低: 上下文信息缺失或者不足,导致无法关联具体业务操作,靠大脑分析上下文信息,然后组装出有效的调用链路,开发人员的精力浪费在分析日志上,而不是定位问题和修复程序逻辑上,导致研发流程效率低下。
  • 日志分散存储:即使是单体应用,日志可能分布在不同的文件,需要手动切换查看,操作不方便。
deepseek_mermaid_20250723_166381.png
deepseek_mermaid_20250723_166381.png

对于同一业务标识,产生的日志大致如下:

代码语言:javascript
代码运行次数:0
运行
复制
// 请求A和B的日志交错输出(按时间戳排序)
[2023-08-15 15:00:00.001] [thread-1] INFO  c.OrderController - 收到订单支付请求: ORD-123
[2023-08-15 15:00:00.001] [thread-2] INFO  c.OrderController - 收到订单支付请求: ORD-123
[2023-08-15 15:00:00.002] [thread-1] INFO  c.OrderService - 开始处理订单支付: ORD-123
[2023-08-15 15:00:00.003] [thread-2] INFO  c.OrderService - 开始处理订单支付: ORD-123
[2023-08-15 15:00:00.003] [thread-1] DEBUG c.OrderDao - 查询订单数据: ORD-123
[2023-08-15 15:00:00.004] [thread-2] WARN  c.OrderService - 等待订单锁: ORD-123
[2023-08-15 15:00:00.005] [thread-1] INFO  c.OrderService - 获取分布式锁成功: ORD-123
[2023-08-15 15:00:00.007] [thread-1] DEBUG c.OrderDao - 更新订单状态: ORD-123 [CREATED → PAID]
[2023-08-15 15:00:00.009] [thread-1] INFO  c.OrderService - 支付处理完成: ORD-123
[2023-08-15 15:00:00.010] [thread-1] INFO  c.OrderService - 释放订单锁: ORD-123
[2023-08-15 15:00:00.011] [thread-2] INFO  c.OrderService - 获取分布式锁成功: ORD-123
[2023-08-15 15:00:00.012] [thread-2] DEBUG c.OrderDao - 查询订单数据: ORD-123
[2023-08-15 15:00:00.013] [thread-2] INFO  c.OrderService - 订单已支付,跳过处理: ORD-123

这样就造成根据业务标识进行日志跟踪和问题排查的时候造成很大困扰,需要筛选、分析出单一请求链路的日志信息。

二、MDC是什么?

1.MDC介绍

MDC是日志框架(如 SLF4J、Logback、Log4j2)提供的一个线程绑定的上下文存储机制,用于在日志中附加额外的上下文信息(如请求 ID、用户 ID、链路追踪 ID 等)。它是一个在日志框架中使用的工具,主要用于在多线程环境中追踪和诊断日志。MDC允许开发人员在日志记录中关联特定线程的上下文信息,例如用户ID、请求ID、会话ID等,以便在日志中更方便地进行追踪和定位问题。

2.MDC的具体作用
  • 关联上下文信息:MDC可以将与当前线程相关的上下文信息(如用户ID、请求ID等)存储起来,并在日志记录中输出,方便后续的日志分析。
  • 方便问题追踪:通过MDC,可以快速定位特定用户、特定请求或特定会话相关的日志,从而更有效地进行问题排查和诊断。
  • 增强日志可读性:MDC可以将上下文信息与日志内容关联起来,使得日志更加清晰和易于理解。
  • 支持分布式追踪:在分布式系统中,MDC可以用于关联不同服务间的日志,实现分布式追踪
3.MDC的工作原理

使用MDC的时候一般会在链路开始阶段执行MDC.put,然后在链路结束时执行MDC.clear,我们简单分析一下其实现原理:

代码语言:javascript
代码运行次数:0
运行
复制
public static void put(String key, String val) throws IllegalArgumentException {
    if (key == null) {
        throw new IllegalArgumentException("key parameter cannot be null");
    }
    if (mdcAdapter == null) {
        throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);
    }
    mdcAdapter.put(key, val);
}

mdcAdapter有多种实现,看一下LogbackMDCAdapter实现:

代码语言:javascript
代码运行次数:0
运行
复制
public void put(String key, String val) throws IllegalArgumentException {
    if (key == null) {
        throw new IllegalArgumentException("key cannot be null");
    }
    Map<String, String> oldMap = copyOnThreadLocal.get();
    Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);

    if (wasLastOpReadOrNull(lastOp) || oldMap == null) {
        Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
        newMap.put(key, val);
    } else {
        oldMap.put(key, val);
    }
}

可以看到MDC的put实际操作的是copyOnThreadLocal,而copyOnThreadLocal是一个ThreadLocal,是线程上下文。

链路结束时执行的clear操作实现如下:

代码语言:javascript
代码运行次数:0
运行
复制
public void clear() {
    lastOperation.set(WRITE_OPERATION);
    copyOnThreadLocal.remove();
}

与put操作类似,clear也会操作当前线程对应的ThreadLocal,执行了remove操作把上下文进行了清理。

也就是说通过MDC可以将我们想要记录的内容或者标识放入当前请求的处理线程,然后在后续逻辑中随时可以拿来使用,比如日志打印。

三、聊一聊请求与线程池

对于springboot服务,默认使用tomcat作为servlet容器启动服务,以http请求接收与处理为例,整体流程大致如下:

所有http请求的处理默认使用tomcat提供的线程池,对于java而言线程是基于native线程的封装,线程池维护线程的创建、闲置处理、销毁的流程,也就是说线程池管理的线程是可以复用的,也就是说不管是tomcat管理的线程池还是spring容器管理的线程池,在使用过程中或者说处理请求过程中会在业务处理的过程中产生和携带上下文信息,使用完成后清除上下文信息,归还线程到线程池并不会立刻销毁线程,这一点是非常重要的,避免线程级别的引用造成内存泄漏,以及对后续请求复用线程造成干扰。

对于MDC而言,本身操作的就是线程级别的ThreadLocal,所以如果要使用MDC做请求和线程级别的链路追踪,那么就需要选择一个合适的请求开始节点通过MDC塞入需要跟踪的上下文信息,然后在请求结束时(无论是正常结束还是异常结束)清除上下文信息即可。

四、链路追踪切入点与传递

对于请求处理链路的追踪并不能局限于http请求,以springboot服务为例,从请求起点或者异步线程独立执行作为基线,需要做链路追踪的切入点还是比较多的。

通过分析,我们尽可能能够覆盖绝大多数需要追踪的链路和切入点,满足后续分析日志和问题排查的诉求。

1.http请求

http请求是web服务中最普遍的请求,对于http请求,我们可以基于框架提供的拦截器进行请求拦截,在请求开始的时候通过MDC塞入线程上下文,然后在后续的处理链路中可以拿到上下文信息进行日志打印以及其他操作。

2.websocket请求

在Spring框架中,HandlerInterceptor(处理器拦截器)默认不会直接拦截WebSocket连接建立后的消息传输,但是会拦截WebSocket握手阶段的 HTTP 请求(即连接建立的初始 HTTP 请求)。请求升级协议到WebSocket后便不会再拦截。

websocket模块有自己的拦截器,比如HandshakeInterceptor,可以通过编写相关实现来嵌入MDC上下文。

3.定时任务

定时任务也是业务开发中比较常用的技术,对于单体应用,可以使用spring自带调度器scheduler,而集群服务会使用中心化任务调度平台,比如xxl-job。

  • 对于自带scheduler调度,可以改造其线程池,自定义任务线程池,在任务触发的时候会使用线程池的线程执行逻辑,那么就可以在执行线程逻辑的时候嵌入MDC上下文。
  • 对于中心化任务调度,以xxl-job为例,任务的触发是有调度平台发起,然后通过http请求调用业务服务的接口完成,那么就可以在job-executor逻辑触发的时候拦截请求嵌入MDC上下文即可。
4.自定义并发请求

在一些负载查询场景,比如订单详情,会关联用户、地址、商品等信息,我个人比较喜欢空间换时间做法,主线程查询订单数据,然后并发线程查询其他关联内容,然后归并到统一返回结果中。

对于这种情况,主流程还是由工作线程完成,我会使用google的并发组件完成并发查询,然后结果集回写到主线程。但是不管是google还是其他并发组件,其底层核心逻辑还是线程池,那么类似情况,我们可以改造线程池即可。

5.异步操作@Async

@Async也是spring自带的模块,提供异步处理能力,和前边类似,异步处理的核心逻辑都是开启新的线程处理业务,@Async只是封装了复杂的线程操作而已,对于这种情况我们改造@Async的线程池,嵌入MDC上下文即可。

6.消费消息

稍微复杂一点的业务都会引入消息服务,接收消息后会处理一些业务逻辑,也是一个完整的请求链路,对于这种情况,以Rocketmq为例,在监听器的处理逻辑入口嵌入MDC上下文即可完成整个链路的最终。

五、基于MDC实现日志链路追踪

上一节分析了请求链路追踪的切入点,接着我们选择几个典型的切入点来实现链路追踪能力。

1.前置准备

引入依赖(logback):

代码语言:javascript
代码运行次数:0
运行
复制
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
</dependency>

修改日志配置文件(logback-spring.xml):

代码语言:javascript
代码运行次数:0
运行
复制
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--日志存储路径-->
    <property name="log" value="/mnt/applogs/" />
    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--输出格式化-->
            <pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按天生成日志文件 -->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件名-->
            <FileNamePattern>${log}/%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>
    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>
</configuration>

这里定义了TRACE_ID,也就是说请求处理开始的时候,需要通过MDC将TRACE_ID放入线程上下文,然后后续日志打印的时候会读取到TRACE_ID并打印到控制台以及日志文件。请求链路分析以及问题排查的时候就可以根据TRACE_ID来展开。

2.http请求追踪

http的请求追踪,可以基于HandlerInterceptor来实现,实现HandlerInterceptor接口重写preHandle,afterCompletion逻辑,在请求开始时嵌入MDC逻辑,请求结束后返回调用之前请求线程的MDC上下文信息,避免造成线程污染。

编写拦截器:

代码语言:javascript
代码运行次数:0
运行
复制
public class TraceInterceptor implements HandlerInterceptor {
    private static final String TRACE_ID = "TRACE_ID";
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String traceId = UUID.randomUUID().toString().replace("-", "");
        //外部调用如果传入了TRACE_ID,直接传递即可
        if (StringUtils.isNotEmpty(request.getHeader("TRACE_ID"))){
            traceId=request.getHeader("TRACE_ID");
        }
        MDC.put(TRACE_ID, traceId);
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                @Nullable Exception ex) {
        //请求处理完成,清理MDC信息                        
        MDC.remove(TRACE_ID);
    }
}

添加拦截器:

代码语言:javascript
代码运行次数:0
运行
复制
@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {
    @Bean
    public TraceInterceptor traceInterceptor() {
        return new TraceInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(traceInterceptor()).addPathPatterns("/**")
                .excludePathPatterns(WHITELIST);
    }
}

编写简单api并发送http请求可以看到日志内容:

这样对于http请求就可以基于MDC传入的上下文信息完成请求链路分析了。

基于HandlerInterceptor实现请求预处理和请求扫尾处理,对于afterCompletion方法一定会执行吗?假如业务逻辑处理出现异常了,afterCompletion还会执行吗?如果不执行的话线程中的MDC上下文就没有清除了,从而导致后续的线程污染、内存泄露等,这是非常危险的,搞清楚这一点也是非常重要的。

http请求处理以及拦截器拦截逻辑的执行都在DispatcherServlet的doDispatch实现:

代码语言:javascript
代码运行次数:0
运行
复制
// DispatcherServlet.java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ... 获取 HandlerExecutionChain ...
    try {
        // 1. 执行 preHandle
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          return;
        }
        // 2. 执行逻辑
        // 3. 执行 postHandle
        mappedHandler.applyPostHandle(processedRequest, response, mv);
        //4.组装结果
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    } catch (Exception ex) {
      // 捕获异常,触发 afterCompletion(传入异常)
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		} catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
		} finally {}
}

这里我们需要关注三个点:

  • applyPreHandle返回false,这里执行拦截器的preHandle逻辑,如果返回false那么清理MDC上下文还会执行吗?
  • 出现异常会执行拦截器的afterCompletion逻辑吗?
  • 正常处理完成会调用afterCompletion清理MDC上下文吗?

这里拦截器预处理如果返回false则会调用triggerAfterCompletion,这里就是调用拦截器afterCompletion逻辑的地方:

从doDispatch方法的异常捕获,会执行triggerAfterCompletion方法,从而也会调用拦截器的afterCompletion方法清理MDC上下文。

对于业务请求正常处理完成,会调用processDispatchResult方法:

代码语言:javascript
代码运行次数:0
运行
复制
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
    @Nullable Exception exception) throws Exception {
  boolean errorView = false;
  //...省略...
  if (mappedHandler != null) {
    // Exception (if any) is already handled..
    mappedHandler.triggerAfterCompletion(request, response, null);
  }
}

这里也会调用triggerAfterCompletion方法执行拦截器的afterCompletion逻辑,从而清理MDC上下文。

也就是说任何一种场景拦截器的afterCompletion方法一定会被执行,所以把清理线程上的MDC上下文没有任何问题。

3.定时任务追踪

定时任务作为切入点的链路追踪实现,我们分为两种类型实现,分别是spring自带的任务调度scheduler和常用的xxl-job。

自带scheduler

spring自带的任务调度框架封装了复杂的调度逻辑,通过注解的方式使用调度能力,那么我们如果想要在调度任务中嵌入MDC链路追踪,那么需要从调度的线程池作为切入点改造。

对于spring自带Scheduler如果想要自定义线程池,一般做法是实现SchedulingConfigurer接口重写configureTasks方法实现:

代码语言:javascript
代码运行次数:0
运行
复制
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(){
          //...改造执行逻辑...
        };
        taskScheduler.setPoolSize(10); // 设置线程池大小
        taskScheduler.setThreadNamePrefix("scheduled-task-pool-"); // 设置线程名称前缀
        taskScheduler.initialize();
        taskRegistrar.setTaskScheduler(taskScheduler);
    }
}

上述configureTasks方法中创建ThreadPoolTaskScheduler时,我们可以使用自己的实现,修改相关线程任务执行逻辑,在实行任务之前在线程中植入MDC上下文,然后执行完成进行清理。

代码语言:javascript
代码运行次数:0
运行
复制
// 包装任务的核心方法
private Runnable wrap(Runnable command) {
    return () -> {
        // 放置traceId
        String traceId=UUID.randomUUID().toString().replace("-", "");
        MDC.put("TRACE_ID", traceId);
        try {
            command.run();
        } finally {
            MDC.clear();
        }
    };
}
private <T> Callable<T> wrap(Callable<T> task) {
    return () -> {
        String traceId=UUID.randomUUID().toString().replace("-", "");
        MDC.put("TRACE_ID", traceId);
        try {
            return task.call();
        } finally {
            MDC.clear();
        }
    };
}

这样在任务触发以及后续逻辑处理的时候都能从线程上下文中拿到MDC信息,从而实现调度任务执行逻辑链路追踪。

xxl-job调度

众所周知,应用集成xxl-job后,启动的时候会通过xxl-job内嵌的EmbedServer启动一个单独的web服务(默认监听端口9999):

这里我们可以改写EmbedServer的逻辑,在ThreadPoolExecutor工作线程池上做文章,改造工作线程池在执行逻辑上嵌入MDC上下文以及清除逻辑,不做这种方式要改造xxl-job源码,改造成本比较高,不推荐这种做法。

当然我们有更简单的解决方案,通过xxl-job来实现任务调度一般使用@XxlJob注解的方式,那么直接在@XxlJob注解标注的方法上注入MDC上下文以及MDC清理逻辑。

代码语言:javascript
代码运行次数:0
运行
复制
@XxlJob("demoJobHandler")
public void execute() {
    // 生成 traceId
    String traceId = UUID.randomUUID().toString().replace("-", "");
    MDC.put("TRACE_ID", traceId); // 存入任务执行线程的 MDC
    try {
        log.info("开始执行任务,traceId={}", traceId);
        // 业务逻辑...
    } finally {
        MDC.clear();
    }
}

这样xxl-job-admin在触发调度的时候通过http协议调用业务服务的run接口触发任务执行,也实现了调度任务执行逻辑链路追踪。

4.消息消费追踪

很多服务都有异步、削峰填谷、广播通知的诉求,都会引入消息服务,消息消费由消息服务器推送消息或者业务服务主动拉取消息来完成业务操作,也是一个完整的请求链路,我们以Rocketmq为例分析一下消费消息的MDC请求链路追踪。

代码语言:javascript
代码运行次数:0
运行
复制
@Component
@RocketMQMessageListener(consumerGroup = MQConstant.xxx,
        topic = "xxx", messageModel = MessageModel.CLUSTERING,
        consumeThreadMax = 5)
public class TestMQReceiver implements RocketMQListener<String> {
    @Override
    public void onMessage(String message) {
        String traceId = UUID.randomUUID().toString().replace("-", "");
        MDC.put("TRACE_ID", traceId); 
        try {
          //...执行消息消费逻辑...
        } catch (Throwable e) {
            log.error("代收订单结果跟踪MQ消费异常", e);
        }  finally {
            MDC.clear();
        }
    }
}

收到消息是向线程中塞入MDC信息,然后消息消费完成无论成功失败都清理掉MDC内容,这样就实现了消息消费的请求链路追踪能力。

六、与其他链路追踪框架的异同

市面上有很多免费开源的请求链路追踪框架比如SkyWalking、Zipkin,但是个人观点是通过MDC实现请求链路追踪与通过框架实现请求链路追踪本质上是殊途同归的,单体应用或者简单的服务拓扑,使用MDC更适合,降低了项目复杂度,并且降低了搭建链路追踪服务的人工成本和资源成本。当然对于一些大型应用或者比较复杂的服务架构,实用链路追踪的优势就比较明显了,并且框架还能提供QPS监控、请求链路自动分析处理、以及告警能力。如下是从一些典型的维度对比MDC与框架之间的异同和优劣:

1.实现机制

MDC

  • 存储方式:基于 ThreadLocal实现,上下文信息与当前线程绑定。
  • 传递方式:仅在同一线程内传递(如主线程→子线程需手动复制 ThreadLocal)。
  • 生命周期:随线程创建/销毁而生效(线程池复用线程时需手动清理)。

链路追踪框架

  • 存储方式:通常使用 Context对象(如 OpenTelemetry 的 Context),支持跨线程、跨服务传递。
  • 传递方式:通过上下文传播机制(如 HTTP 头、消息头、RPC 元数据)在跨服务/线程时传递追踪 ID(如 traceId、spanId)。
  • 生命周期:与请求生命周期绑定(从请求发起→处理完成→响应返回)。
2.功能范围

MDC

  • 核心功能:为日志添加自定义上下文(如 traceId、userId)。
  • 扩展能力:仅支持日志级别的上下文关联,无法追踪调用链路的时间、延迟、依赖等细节。

链路追踪框架

  • 核心功能:
    • 追踪请求的全链路(如 traceId标识一次完整请求);
    • 记录每个节点的 span(如服务 A 处理耗时、服务 B 调用延迟);
    • 收集指标(如 QPS、错误率)和依赖拓扑(如服务间调用关系)。
  • 扩展能力:支持分布式事务、性能分析、告警等高级功能。
3.使用场景

MDC

  • 单体应用/简单微服务:同一服务内多个模块/方法的日志关联(如用户下单→扣库存→生成订单的日志链)。
  • 轻量级需求:仅需通过 traceId关联日志,无需复杂的链路分析。

链路追踪框架

  • 分布式系统:跨服务、跨机器的请求链路追踪(如用户请求经过网关→服务 A→服务 B→数据库)。
  • 复杂场景:需要分析调用延迟、定位慢接口、监控服务依赖关系。
4.互补性
  • 链路追踪框架生成追踪 ID:由链路追踪框架(如 OpenTelemetry)生成全局唯一的 traceId,并通过上下文传播机制传递到各个服务。
  • MDC 注入追踪 ID:在每个服务的日志输出前,将 traceId存入MDC,使日志自动携带该标识。
  • 日志与链路关联:通过日志中的traceId,可在链路追踪平台(如Jaeger)中快速定位对应的调用链。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景描述
  • 二、MDC是什么?
    • 1.MDC介绍
    • 2.MDC的具体作用
    • 3.MDC的工作原理
  • 三、聊一聊请求与线程池
  • 四、链路追踪切入点与传递
    • 1.http请求
    • 2.websocket请求
    • 3.定时任务
    • 4.自定义并发请求
    • 5.异步操作@Async
    • 6.消费消息
  • 五、基于MDC实现日志链路追踪
    • 1.前置准备
    • 2.http请求追踪
    • 3.定时任务追踪
    • 4.消息消费追踪
  • 六、与其他链路追踪框架的异同
    • 1.实现机制
    • 2.功能范围
    • 3.使用场景
    • 4.互补性
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档