MDC在多線程中的使用方式
一、了解MDC
MDC(Mapped Diagnostic Context,映射調(diào)試上下文)是 slf4j提供的一種輕量級的日志跟蹤工具。
Log4j、Logback或者Log4j2等日志中最常見區(qū)分同一個(gè)請求的方式是通過線程名,而如果請求量大,線程名在相近的時(shí)間內(nèi)會(huì)有很多重復(fù)的而無法分辨,因此引出了trace-id,即在接收到的時(shí)候生成唯一的請求id,在整個(gè)執(zhí)行鏈路中帶上此唯一id.
MDC.java本身不提供傳遞traceId的能力,真正提供能力的是MDCAdapter接口的實(shí)現(xiàn)。
比如Log4j的是Log4jMDCAdapter,Logback的是LogbackMDCAdapter。
二、MDC普通使用
MDC.put(“trace-id”, traceId); // 添加traceId MDC.remove(“trace-id”); // 移除traceId
在日志配置文件中<pattern>節(jié)點(diǎn)中以%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操作的當(dāng)前線程中獲取到值。
那是因?yàn)镸DC的實(shí)現(xiàn)原理主要就是ThreadLocal,ThreadLocal只對當(dāng)前線程有效。
三、多線程中的MDC
要破除ThreadLocal只對當(dāng)前線程有線的方法有兩種:
- 一種是JDK自帶的、ThreadLocal的擴(kuò)展類InheritableThreadLocal,子線程會(huì)拷貝父線程中的變量值
- 一種是引入alibabatransmittable-thread-local包的TransmittableThreadLocal實(shí)現(xiàn)
據(jù)我所知,只有子線程獲取父線程,不能父線程獲取子線程的變量,如果有知道父獲取子的方法,麻煩說一下
Sl4j本身的提供的BasicMDCAdapter就是基于InheritableThreadLocal實(shí)現(xiàn)了線程間傳遞,但log4j、logback這兩個(gè)實(shí)際的日志實(shí)現(xiàn)沒有提供,log4j2提供了但默認(rèn)關(guān)閉。
1.log4j2主要是根據(jù)isThreadContextMapInheritable變量控制的,
有兩種方法:
- log4j2.component.properties文件中配置
- 類型模塊中定義,System.setProperty("log4j2.isThreadContextMapInherimeble", "true");
2.lob4j和logback需要自己手動(dòng)實(shí)現(xiàn),主要有兩種方法,一是手動(dòng)在線程中的處理,一種是重寫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設(shè)置給子線程 */ 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 { // 清除子線程的,避免內(nèi)存溢出,就和ThreadLocal.remove()一個(gè)原因 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: 把當(dāng)前的traceId透傳到子線程特意加的實(shí)現(xiàn)。重點(diǎn)就是 MDC.getCopyOfContextMap(),此方法獲取當(dāng)前線程(父線程)的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; /** * 線程池配置。重點(diǎn)就是把ThreadPoolTaskExecutor換成ThreadPoolMdcExecutor **/ @Configuration public class ThreadPoolConfig { @Resource private ThreadPoolProperties threadPoolProperties; /** * 業(yè)務(wù)用到的線程池 * @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; } }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于ThreadLocal的用法和說明及注意事項(xiàng)
這篇文章主要介紹了關(guān)于ThreadLocal的用法和說明及注意事項(xiàng),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05spring動(dòng)態(tài)控制定時(shí)任務(wù)的實(shí)現(xiàn)
在實(shí)際項(xiàng)目中,經(jīng)常需要?jiǎng)討B(tài)的控制定時(shí)任務(wù),比如通過接口增加、啟動(dòng)、停止、刪除定時(shí)任務(wù),本文主要介紹了spring動(dòng)態(tài)控制定時(shí)任務(wù)的實(shí)現(xiàn),感興趣的可以了解一下2024-01-01Java中的關(guān)鍵字_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
關(guān)鍵字也稱為保留字,是指Java語言中規(guī)定了特定含義的標(biāo)示符。對于保留字,用戶只能按照系統(tǒng)規(guī)定的方式使用,不能自行定義2017-04-04Java創(chuàng)建類模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java創(chuàng)建類模式的相關(guān)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08Java基于TCP協(xié)議socket網(wǎng)絡(luò)編程的文件傳送的實(shí)現(xiàn)
這篇文章主要介紹了Java基于TCP協(xié)議socket網(wǎng)絡(luò)編程的文件傳送的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Springboot+Mybatis中typeAliasesPackage正則掃描實(shí)現(xiàn)方式
這篇文章主要介紹了Springboot+Mybatis中typeAliasesPackage正則掃描實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Spring Cloud中關(guān)于Feign的常見問題總結(jié)
這篇文章主要給大家介紹了Spring Cloud中關(guān)于Feign的常見問題,文中通過示例代碼介紹的很詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-02-02Java8實(shí)現(xiàn)任意參數(shù)的鏈棧
這篇文章主要為大家詳細(xì)介紹了Java8實(shí)現(xiàn)任意參數(shù)的鏈棧,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10