MDC在多線程中的使用方式
一、了解MDC
MDC(Mapped Diagnostic Context,映射調試上下文)是 slf4j提供的一種輕量級的日志跟蹤工具。
Log4j、Logback或者Log4j2等日志中最常見區(qū)分同一個請求的方式是通過線程名,而如果請求量大,線程名在相近的時間內會有很多重復的而無法分辨,因此引出了trace-id,即在接收到的時候生成唯一的請求id,在整個執(zhí)行鏈路中帶上此唯一id.
MDC.java本身不提供傳遞traceId的能力,真正提供能力的是MDCAdapter接口的實現(xiàn)。
比如Log4j的是Log4jMDCAdapter,Logback的是LogbackMDCAdapter。
二、MDC普通使用
MDC.put(“trace-id”, traceId); // 添加traceId MDC.remove(“trace-id”); // 移除traceId
在日志配置文件中<pattern>節(jié)點中以%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的實現(xiàn)原理主要就是ThreadLocal,ThreadLocal只對當前線程有效。
三、多線程中的MDC
要破除ThreadLocal只對當前線程有線的方法有兩種:
- 一種是JDK自帶的、ThreadLocal的擴展類InheritableThreadLocal,子線程會拷貝父線程中的變量值
- 一種是引入alibabatransmittable-thread-local包的TransmittableThreadLocal實現(xiàn)
據(jù)我所知,只有子線程獲取父線程,不能父線程獲取子線程的變量,如果有知道父獲取子的方法,麻煩說一下
Sl4j本身的提供的BasicMDCAdapter就是基于InheritableThreadLocal實現(xiàn)了線程間傳遞,但log4j、logback這兩個實際的日志實現(xiàn)沒有提供,log4j2提供了但默認關閉。
1.log4j2主要是根據(jù)isThreadContextMapInheritable變量控制的,
有兩種方法:
- log4j2.component.properties文件中配置
- 類型模塊中定義,System.setProperty("log4j2.isThreadContextMapInherimeble", "true");
2.lob4j和logback需要自己手動實現(xiàn),主要有兩種方法,一是手動在線程中的處理,一種是重寫LogbackMDCAdapter。
本文以在線程池中創(chuàng)建為例
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透傳到子線程特意加的實現(xiàn)。重點就是 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; /** * 業(yè)務用到的線程池 * @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; } }
總結
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Java創(chuàng)建類模式_動力節(jié)點Java學院整理
這篇文章主要為大家詳細介紹了Java創(chuàng)建類模式的相關方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08Java基于TCP協(xié)議socket網(wǎng)絡編程的文件傳送的實現(xiàn)
這篇文章主要介紹了Java基于TCP協(xié)議socket網(wǎng)絡編程的文件傳送的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12Springboot+Mybatis中typeAliasesPackage正則掃描實現(xiàn)方式
這篇文章主要介紹了Springboot+Mybatis中typeAliasesPackage正則掃描實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07