最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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)多線程處理任務(wù)時傳遞日志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);
? ? // 線程池名的前綴:設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池
? ? executor.setThreadNamePrefix("test-");
? ? // 拒絕策略為調(diào)用者執(zhí)行
? ? executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
? ? executor.initialize();
? ? return executor;
}

線程間傳遞Traceid問題

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

在這里分享下如何快速定位多線程環(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);
? ? ? ? //隊列最大長度,一般需要設(shè)置值>=notifyScheduledMainExecutor.maxNum;默認為Integer.MAX_VALUE
? ? ? ? taskExecutor.setQueueCapacity(2000);
? ? ? ? //線程池維護線程所允許的空閑時間,默認為60s
? ? ? ? taskExecutor.setKeepAliveSeconds(60);
? ? ? ? //線程池對拒絕任務(wù)(無線程可用)的處理策略
? ? ? ? taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
? ? ? ? // 初始化線程池
? ? ? ? taskExecutor.initialize();
? ? ? ? return ?taskExecutor;
? ? }

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

總結(jié)

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

相關(guān)文章

  • java中“==“和equals()的區(qū)別詳解

    java中“==“和equals()的區(qū)別詳解

    這篇文章主要給大家介紹了關(guān)于java中“==“和equals()區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Springboot的maven間接依賴的實現(xiàn)

    Springboot的maven間接依賴的實現(xiàn)

    這篇文章主要介紹了Springboot的maven間接依賴的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • MyBatis-Plus UpdateWrapper 使用常見陷阱和解決方案

    MyBatis-Plus UpdateWrapper 使用常見陷阱和解決方案

    MyBatis-Plus是Mybatis的一個增強,簡化了Mybatis的開發(fā)過程,不僅保持了Mybatis原有的功能,而且在無代碼侵略下增加了許多的增強的功能,提供了豐富的CRUD操作,單表的CRUD操作無需編寫SQL語句,本文介紹的是UpdateWrapper的常見陷阱和對應(yīng)的解決方案,感興趣的朋友一起看看吧
    2024-08-08
  • Spring Security如何使用URL地址進行權(quán)限控制

    Spring Security如何使用URL地址進行權(quán)限控制

    這篇文章主要介紹了Spring Security如何使用URL地址進行權(quán)限控制,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12
  • SpringBoot MDC全鏈路調(diào)用日志跟蹤實現(xiàn)詳解

    SpringBoot MDC全鏈路調(diào)用日志跟蹤實現(xiàn)詳解

    這篇文章主要為大家介紹了SpringBoot MDC全鏈路調(diào)用日志跟蹤實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • Spring Boot集成Druid查看配置是否生效的方法

    Spring Boot集成Druid查看配置是否生效的方法

    本文主要介紹了Spring Boot集成Druid查看配置是否生效的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • Java web的讀取Excel簡單實例代碼

    Java web的讀取Excel簡單實例代碼

    下面小編就為大家?guī)硪黄狫ava web的讀取Excel簡單實例代碼。小編覺挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-06-06
  • Java后端登錄實現(xiàn)返回token

    Java后端登錄實現(xiàn)返回token

    本文主要介紹了Java后端登錄實現(xiàn)返回token,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Spring?MVC注解@ControllerAdvice的工作原理解讀

    Spring?MVC注解@ControllerAdvice的工作原理解讀

    本文解析SpringMVC中@ControllerAdvice注解,通過結(jié)合@ExceptionHandler、@ModelAttribute、@InitBinder實現(xiàn)全局異常處理、數(shù)據(jù)模型和綁定配置,解析其通過容器初始化和緩存機制提取并應(yīng)用定義的原理
    2025-08-08
  • 如何在Spring boot加入shiro支持

    如何在Spring boot加入shiro支持

    這篇文章主要介紹了如何在Spring boot加入shiro支持,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12

最新評論

余江县| 金乡县| 奉新县| 霍邱县| 乌拉特中旗| 伊川县| 沭阳县| 二手房| 中卫市| 尼玛县| 赤壁市| 平顺县| 高阳县| 师宗县| 荣昌县| 肇源县| 黄骅市| 清远市| 邢台县| 莆田市| 苍溪县| 凌源市| 沛县| 漳浦县| 腾冲县| 隆尧县| 噶尔县| 莱州市| 青阳县| 滁州市| 丰县| 景德镇市| 汾西县| 六盘水市| 峨山| 沐川县| 清流县| 陈巴尔虎旗| 广东省| 朝阳区| 忻城县|