欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java多線程之間日志traceId傳遞方式

 更新時間:2023年08月28日 08:42:25   作者:丶只有影子  
這篇文章主要介紹了Java多線程之間日志traceId傳遞方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

Java多線程之間日志traceId傳遞

在生產(chǎn)環(huán)境中,由于處在并發(fā)環(huán)境,所以日志輸出的順序散落在各個不同行,通過traceId就能夠快速定位到同一個請求的多個不同的日志輸出,可以很方便地跟蹤請求并定位問題。

但是,如果在代碼中使用了多線程,那么就會發(fā)現(xiàn),新開的線程不會攜帶父線程traceId。于是,通過繼承父線程的MDC上下文信息,使得新開的線程與父線程保持一致的traceId。

MDC說明

MDC(Mapped Diagnostic Context)是一種常用的日志記錄技術(shù),MDC可以將關(guān)鍵信息存儲在線程上下文中,并在需要時將其傳遞到調(diào)用鏈的不同組件中。

使用MDC傳遞日志的好處:

  • 方便跟蹤請求:通過 MDC,可以在整個請求生命周期中記錄和傳遞關(guān)鍵信息,例如請求 ID、用戶 ID 等,這樣可以方便地跟蹤請求并定位問題。
  • 提高調(diào)試效率:MDC 可以存儲調(diào)用鏈中各個組件的上下文信息,從而使得在調(diào)試時可以更快速地診斷問題,縮短故障排除時間。
  • 支持分布式系統(tǒng):在分布式系統(tǒng)中,MDC 可以在不同節(jié)點之間傳遞關(guān)鍵信息,使得在跨節(jié)點調(diào)用時可以快速定位問題。
  • 提高代碼可讀性:MDC 記錄的上下文信息可以被日志輸出格式化為易于閱讀的形式,提升代碼可讀性。

實現(xiàn)代碼

/**
?* 繼承ThreadPoolTaskExecutor,實現(xiàn)多線程處理任務時傳遞日志traceId
?*/
public class ThreadPoolTaskExecutorMdcUtil extends ThreadPoolTaskExecutor {
? ? @Override
? ? public void execute(Runnable task) {
? ? ? ? super.execute(wrap(task));
? ? }
? ? @Override
? ? public <T> Future<T> submit(Callable<T> task) {
? ? ? ? return super.submit(wrap(task));
? ? }
? ? @Override
? ? public Future<?> submit(Runnable task) {
? ? ? ? return super.submit(wrap(task));
? ? }
? ? private <T> Callable<T> wrap(final Callable<T> callable) {
? ? ? ? // 獲取當前線程的MDC上下文信息
? ? ? ? Map<String, String> context = MDC.getCopyOfContextMap();
? ? ? ? return () -> {
? ? ? ? ? ? if (context != null) {
? ? ? ? ? ? ? ? // 傳遞給子線程
? ? ? ? ? ? ? ? MDC.setContextMap(context);
? ? ? ? ? ? }
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? return callable.call();
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? // 清除MDC上下文信息,避免造成內(nèi)存泄漏
? ? ? ? ? ? ? ? MDC.clear();
? ? ? ? ? ? }
? ? ? ? };
? ? }
? ? private Runnable wrap(final Runnable runnable) {
? ? ? ? Map<String, String> context = MDC.getCopyOfContextMap();
? ? ? ? return () -> {
? ? ? ? ? ? if (context != null) {
? ? ? ? ? ? ? ? MDC.setContextMap(context);
? ? ? ? ? ? }
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? runnable.run();
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? // 清除MDC上下文信息,避免造成內(nèi)存泄漏
? ? ? ? ? ? ? ? MDC.clear();
? ? ? ? ? ? }
? ? ? ? };
? ? }
}

之后只要像正常的使用線程池一樣使用ThreadPoolTaskExecutorMdcUtil類即可。

例如,注入一個線程池Bean代碼示例:

@Bean("thread-pool-receive")
public ThreadPoolTaskExecutor receiveThreadPoolExecutor() {
? ? // new的是自定義的線程池
? ? ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorMdcUtil();
? ? executor.setCorePoolSize(1);
? ? executor.setMaxPoolSize(10);
? ? // 緩存隊列
? ? executor.setQueueCapacity(10000);
? ? // 允許線程的空閑時間60秒:
? ? executor.setKeepAliveSeconds(60);
? ? // 線程池名的前綴:設置好了之后可以方便我們定位處理任務所在的線程池
? ? executor.setThreadNamePrefix("test-");
? ? // 拒絕策略為調(diào)用者執(zhí)行
? ? executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
? ? executor.initialize();
? ? return executor;
}

線程間傳遞Traceid問題

作為一個程序員,在工作當中排查問題是很常見的,但在多線程的情況下,想通過日志跟蹤問題,對于初學者是有點困難的。

在這里分享下如何快速定位多線程環(huán)境下的調(diào)用鏈路,方便調(diào)用日志的查看以及問題的定位

方案

在日志打印時增加Traceid, 方便整個調(diào)用鏈路的追蹤

  • 同步調(diào)用: 能根據(jù)日志打印的Traceid追蹤到整條調(diào)用鏈路
  • 異步調(diào)用: 如果不做其他處理異步調(diào)用的程序打印的日志會丟失Traceid,也就沒法通過這個Traceid查看調(diào)用鏈路。

這時我們就需要對異步調(diào)用的程序進行處理,使得異步調(diào)用時日志文件也能輸出Traceid,并通過Traceid查看調(diào)用鏈路

實現(xiàn)

異步調(diào)用的開啟方式大致可為2種,

1、 new Thread()

2、線程池技術(shù)

在這里我們講的是利用線程池執(zhí)行異步操作,所以我們需要對線程池進行改造,使得其能傳遞Traceid,并在后續(xù)的程序執(zhí)行打印日志時能輸出Traceid

我們知道異步調(diào)用主要的方式有: Callable, Runnable

不錯,到這里我們要做的就是對Callable, Runnable等方法進行封裝,使得其能正確的幫我們傳遞Traceid

傳遞Traceid利用都了日志框架中的MDC工具

我們先定義一個工具類,用于生成Traceid

public class ThreadMdcUtil {
? ? public static String createTraceId() {
? ? ? ? String uuid = UUID.randomUUID().toString();
? ? ? ? return DigestUtils.md5Hex(uuid).substring(8, 24);
? ? }
? ? public static void setTraceIdIfAbsent() {
? ? ? ? if (MDC.get(CommonConstant.LOG_TRACE_ID) == null) {
? ? ? ? ? ? MDC.put(CommonConstant.LOG_TRACE_ID, createTraceId());
? ? ? ? }
? ? }
? ? public static String getTraceId() {
? ? ? ? return MDC.get(CommonConstant.LOG_TRACE_ID);
? ? }
? ? public static void setTraceId() {
? ? ? ? MDC.put(CommonConstant.LOG_TRACE_ID, createTraceId());
? ? }
? ? public static void setTraceId(String traceId) {
? ? ? ? MDC.put(CommonConstant.LOG_TRACE_ID, traceId);
? ? }
? ? public static void clear() {
? ? ? ? MDC.clear();
? ? }
}

有了Traceid,接下來要做的就是對線程里面的2個主要的方法進行改造,

改造方案如下:

public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
? ? ? ? return () -> {
? ? ? ? ? ? if (context == null) {
? ? ? ? ? ? ? ? MDC.clear();
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? MDC.setContextMap(context);
? ? ? ? ? ? }
? ? ? ? ? ? setTraceIdIfAbsent();
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? return callable.call();
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? MDC.clear();
? ? ? ? ? ? }
? ? ? ? };
? ? }
? ? public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
? ? ? ? return () -> {
? ? ? ? ? ? if (context == null) {
? ? ? ? ? ? ? ? MDC.clear();
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? MDC.setContextMap(context);
? ? ? ? ? ? }
? ? ? ? ? ? setTraceIdIfAbsent();
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? runnable.run();
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? MDC.clear();
? ? ? ? ? ? }
? ? ? ? };
? ? }

對線程的2個主要的方法進行改造之后,我們要使得程序日志正確打印傳遞的Traceid 我們還需要進行其他的處理,

需要讓程序需要用到封裝之后的方法,不然之前做的都是無用功,那么我們需要如何處理呢?

上面提到要利用線程池,但是我們?nèi)绾巫尵€程池使用改造之后的2個方法呢?

在這我們要做的就是對線程池進行封裝處理,重寫線程池的方法,讓其用到我們處理后的線程方法。

public class ThreadPoolMdcWrapper extends ThreadPoolTaskExecutor {
? ? public ThreadPoolMdcWrapper() {
? ? }
? ? @Override
? ? public void execute(Runnable task) {
? ? ? ? super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
? ? }
? ? @Override
? ? public void execute(Runnable task, long startTimeout) {
? ? ? ? super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), startTimeout);
? ? }
? ? @Override
? ? public <T> Future<T> submit(Callable<T> task) {
? ? ? ? return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
? ? }
? ? @Override
? ? public Future<?> submit(Runnable task) {
? ? ? ? return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
? ? }
? ? @Override
? ? public ListenableFuture<?> submitListenable(Runnable task) {
? ? ? ? return super.submitListenable(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
? ? }
? ? @Override
? ? public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
? ? ? ? return super.submitListenable(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
? ? }
}

繼承ThreadPoolTaskExecutor ,重寫線程執(zhí)行的方法。

到這我們就做完了大部分的準備工作,還剩下最關(guān)鍵的就是讓程序用到我們封裝后的線程池。

我們可以在聲明線程池的時候,直接使用我們封裝好的線程池(因為繼承了ThreadPoolTaskExecutor)

@Bean
? ? public ThreadPoolTaskExecutor taskExecutor() {
? ? ? ? ThreadPoolTaskExecutor taskExecutor = new ThreadPoolMdcWrapper();
? ? ? ? //核心線程數(shù),默認為1
? ? ? ? taskExecutor.setCorePoolSize(1);
? ? ? ? //最大線程數(shù),默認為Integer.MAX_VALUE
? ? ? ? taskExecutor.setMaxPoolSize(200);
? ? ? ? //隊列最大長度,一般需要設置值>=notifyScheduledMainExecutor.maxNum;默認為Integer.MAX_VALUE
? ? ? ? taskExecutor.setQueueCapacity(2000);
? ? ? ? //線程池維護線程所允許的空閑時間,默認為60s
? ? ? ? taskExecutor.setKeepAliveSeconds(60);
? ? ? ? //線程池對拒絕任務(無線程可用)的處理策略
? ? ? ? taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
? ? ? ? // 初始化線程池
? ? ? ? taskExecutor.initialize();
? ? ? ? return ?taskExecutor;
? ? }

到這我們所做的準備工作,改造工作也就結(jié)束了,剩下的就是使用了。只要在程序異步調(diào)用時,利用聲明好的taskExecutor線程池進行調(diào)用,就可以在線程上下文正確傳遞Traceid了。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • spring boot 中設置默認網(wǎng)頁的方法

    spring boot 中設置默認網(wǎng)頁的方法

    這篇文章主要介紹了spring boot 中設置默認網(wǎng)頁的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • java使用Runtime執(zhí)行系統(tǒng)命令遇到的問題

    java使用Runtime執(zhí)行系統(tǒng)命令遇到的問題

    這篇文章主要介紹了java使用Runtime執(zhí)行系統(tǒng)命令遇到的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Mybatis Plus 代碼生成器的實現(xiàn)

    Mybatis Plus 代碼生成器的實現(xiàn)

    這篇文章主要介紹了Mybatis Plus 代碼生成器的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • Spring?Security實現(xiàn)HTTP認證

    Spring?Security實現(xiàn)HTTP認證

    本文主要介紹了Spring?Security實現(xiàn)HTTP認證,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧<BR>
    2022-06-06
  • @TableField注解之深入理解與應用方式

    @TableField注解之深入理解與應用方式

    在現(xiàn)代軟件開發(fā)中,@TableField注解作為MyBatis-Plus中的一個重要特性,用于定義實體類字段與數(shù)據(jù)庫表字段的映射關(guān)系,本文詳細介紹了@TableField注解的使用場景、屬性及其在實際開發(fā)中的應用,包括字段名稱映射、非數(shù)據(jù)庫字段標識、字段填充策略
    2024-10-10
  • 淺談Java多線程編程中Boolean常量的同步問題

    淺談Java多線程編程中Boolean常量的同步問題

    這篇文章主要介紹了淺談Java多線程編程中Boolean常量的同步問題,主要針對線程之間同步了不同的布爾對象的問題,需要的朋友可以參考下
    2015-10-10
  • Spring Data JPA 建立表的聯(lián)合主鍵

    Spring Data JPA 建立表的聯(lián)合主鍵

    這篇文章主要介紹了Spring Data JPA 建立表的聯(lián)合主鍵。本文詳細的介紹了2種方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-04-04
  • 使用Post方式提交數(shù)據(jù)到Tomcat服務器的方法

    使用Post方式提交數(shù)據(jù)到Tomcat服務器的方法

    這篇將介紹使用Post方式提交數(shù)據(jù)到服務器,由于Post的方式和Get方式創(chuàng)建Web工程是一模一樣的,只用幾個地方的代碼不同,這篇文章主要介紹了使用Post方式提交數(shù)據(jù)到Tomcat服務器的方法,感興趣的朋友一起學習吧
    2016-04-04
  • mybatis嵌套循環(huán)map方式(高級用法)

    mybatis嵌套循環(huán)map方式(高級用法)

    這篇文章主要介紹了mybatis嵌套循環(huán)map方式(高級用法),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • SpringCloud之Zuul網(wǎng)關(guān)原理及其配置講解

    SpringCloud之Zuul網(wǎng)關(guān)原理及其配置講解

    這篇文章主要介紹了SpringCloud之Zuul網(wǎng)關(guān)原理及其配置講解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03

最新評論