MDC在多线程中的使用方式

 更新时间:2023年09月22日 14:20:14   作者:EverSpring  
这篇文章主要介绍了MDC在多线程中的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

一、了解MDC

MDC(Mapped Diagnostic Context,映射调试上下文)是 slf4j提供的一种轻量级的日志跟踪工具。

Log4j、Logback或者Log4j2等日志中最常见区分同一个请求的方式是通过线程名,而如果请求量大,线程名在相近的时间内会有很多重复的而无法分辨,因此引出了trace-id,即在接收到的时候生成唯一的请求id,在整个执行链路中带上此唯一id.

MDC.java本身不提供传递traceId的能力,真正提供能力的是MDCAdapter接口的实现。

比如Log4j的是Log4jMDCAdapter,Logback的是LogbackMDCAdapter。

二、MDC普通使用

MDC.put(“trace-id”, traceId); // 添加traceId
MDC.remove(“trace-id”); // 移除traceId

在日志配置文件中<pattern>节点中以%X{trace-id}取出,

比如:

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [uniqid-%X{trace-id}] %logger{50}-%line - %m%n</pattern>

以上方式,只能在做put操作的当前线程中获取到值。

那是因为MDC的实现原理主要就是ThreadLocal,ThreadLocal只对当前线程有效。

三、多线程中的MDC

要破除ThreadLocal只对当前线程有线的方法有两种:

  • 一种是JDK自带的、ThreadLocal的扩展类InheritableThreadLocal,子线程会拷贝父线程中的变量值
  • 一种是引入alibabatransmittable-thread-local包的TransmittableThreadLocal实现

据我所知,只有子线程获取父线程,不能父线程获取子线程的变量,如果有知道父获取子的方法,麻烦说一下

Sl4j本身的提供的BasicMDCAdapter就是基于InheritableThreadLocal实现了线程间传递,但log4j、logback这两个实际的日志实现没有提供,log4j2提供了但默认关闭。

1.log4j2主要是根据isThreadContextMapInheritable变量控制的,

有两种方法:

  • log4j2.component.properties文件中配置
  • 类型模块中定义,System.setProperty("log4j2.isThreadContextMapInherimeble", "true");

2.lob4j和logback需要自己手动实现,主要有两种方法,一是手动在线程中的处理,一种是重写LogbackMDCAdapter。

本文以在线程池中创建为例

import org.slf4j.MDC;
import org.springframework.util.CollectionUtils;
import java.util.Map;
import java.util.concurrent.Callable;
/**
 * @desc: 定义MDC工具类,支持Runnable和Callable两种,目的就是为了把父线程的traceId设置给子线程
 */
public class MdcUtil {
    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
        return () -> {
            if (CollectionUtils.isEmpty(context)) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            try {
                return callable.call();
            } finally {
                // 清除子线程的,避免内存溢出,就和ThreadLocal.remove()一个原因
                MDC.clear();
            }
        };
    }
    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (CollectionUtils.isEmpty(context)) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
/**
 * @desc: 把当前的traceId透传到子线程特意加的实现。重点就是 MDC.getCopyOfContextMap(),此方法获取当前线程(父线程)的traceId
 */
public class ThreadPoolMdcExecutor extends ThreadPoolTaskExecutor {
    @Override
    public void execute(Runnable task) {
        super.execute(MdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(MdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(MdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
}
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import javax.annotation.Resource;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
 * 线程池配置。重点就是把ThreadPoolTaskExecutor换成ThreadPoolMdcExecutor
 **/
@Configuration
public class ThreadPoolConfig {
    @Resource
    private ThreadPoolProperties threadPoolProperties;
    /**
     * 业务用到的线程池
     * @return
     */
    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolMdcExecutor();
        executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
        executor.setCorePoolSize(threadPoolProperties.getCorePoolSize());
        executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
        executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
        RejectedExecutionHandler handler = ReflectUtil.newInstance(threadPoolProperties.getRejectedExecutionHandler().getClazz());
        executor.setRejectedExecutionHandler(handler);
        return executor;
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • HashMap底层数据结构详细解析

    HashMap底层数据结构详细解析

    这篇文章主要介绍了HashMap底层数据结构详细解析,HashMap作为开发中常用的数据结构,也是面试中经常被问的知识点,因此作为开发者应该尽可能多的理解其底层的数据结构,需要的朋友可以参考下
    2023-11-11
  • SpringBoot Freemarker基础配置与使用方式

    SpringBoot Freemarker基础配置与使用方式

    文章主要介绍了在Spring Boot项目中使用FreeMarker模板引擎的基础配置、使用方法和注意事项,包括依赖引入、配置文件设置、数据类型调用、运算符支持、时间格式转换、逻辑表达式等
    2026-05-05
  • 使用logstash同步mysql数据到elasticsearch实现

    使用logstash同步mysql数据到elasticsearch实现

    这篇文章主要为大家介绍了使用logstash同步mysql数据到elasticsearch实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • 使用System.exit()来优雅地终止SpringBoot项目的代码示例

    使用System.exit()来优雅地终止SpringBoot项目的代码示例

    System.exit() 方法是 Java 中用于退出程序的方法,它接受一个整数参数,通常被用来指示程序的退出状态,本文给大家介绍了如何使用System.exit()来优雅地终止SpringBoot项目,需要的朋友可以参考下
    2024-08-08
  • java实现输出文件夹下某个格式的所有文件实例代码

    java实现输出文件夹下某个格式的所有文件实例代码

    这篇文章主要介绍了java实现输出文件夹下某个格式的所有文件,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-06-06
  • 使用SpringBoot注解方式处理事务回滚实现

    使用SpringBoot注解方式处理事务回滚实现

    这篇文章主要介绍了使用SpringBoot注解方式处理事务回滚实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Java生态中的NLP框架详解

    Java生态中的NLP框架详解

    Java生态系统中提供了多个强大的自然语言处理(NLP)框架,今天通过本文给大家介绍Java生态中的NLP框架,感兴趣的朋友一起看看吧
    2025-06-06
  • Java中List集合去重的几种方式详细解析

    Java中List集合去重的几种方式详细解析

    这篇文章主要介绍了Java中List集合去重的几种方式详细解析,在日常的业务开发中,偶尔会遇到需要将 List 集合中的重复数据去除掉的场景,那么今天我们来看看几种LIst集合去重的方式,需要的朋友可以参考下
    2023-11-11
  • 关于Future机制原理及解析

    关于Future机制原理及解析

    这篇文章主要介绍了关于Future机制原理,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Java elasticsearch安装以及部署教程

    Java elasticsearch安装以及部署教程

    这篇文章主要介绍了elasticsearch安装以及部署教程,内涵详细的图文展示与案例介绍,能够帮助你从0成功部署这个框架,需要的朋友可以参考下
    2021-06-06

最新评论