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

SpringBoot?MDC全局鏈路最新完美解決方案

 更新時間:2023年08月09日 08:58:19   作者:愛叨叨的程序狗  
MDC 在 Spring Boot 中的作用是為日志事件提供上下文信息,并將其與特定的請求、線程或操作關聯起來,通過使用 MDC,可以更好地理解和分析日志,并在多線程環(huán)境中確保日志的準確性和一致性,這篇文章主要介紹了SpringBoot?MDC全局鏈路解決方案,需要的朋友可以參考下

需求

在訪問量較大的分布式系統(tǒng)中,時時刻刻在打印著巨量的日志,當我們需要排查問題時,需要從巨量的日志信息中找到本次排查內容的日志是相對復雜的,那么,如何才能使日志看起來邏輯清晰呢?如果每一次請求都有一個全局唯一的id,當我們需要排查時,根據其他日志打印關鍵字定位到對應請求的全局唯一id,再根據id去搜索、篩選即可找到對應請求全流程的日志信息。接下來就是需要找一種方案,可以生成全局唯一id和在不同的線程中存儲這個id。

解決方案

LogBack這個日志框架提供了MDC( Mapped Diagnostic Context,映射調試上下文 ) 這個功能,MDC可以理解為與線程綁定的數據存儲器。數據可以被當前線程訪問,當前線程的子線程會繼承其父線程中MDC的內容。MDC 在 Spring Boot 中的作用是為日志事件提供上下文信息,并將其與特定的請求、線程或操作關聯起來。通過使用 MDC,可以更好地理解和分析日志,并在多線程環(huán)境中確保日志的準確性和一致性。此外,MDC 還可以用于日志審計、故障排查和跟蹤特定操作的執(zhí)行路徑。

代碼

實現日志打印全局鏈路唯一id的功能,需要三個信息:

  • 全局唯一ID生成器
  • 請求攔截器
  • 自定義線程池(可選)
  • 日志配置

全局唯一ID生成器

生成器可選方案有:

UUID,快速隨機生成、極小概率重復Snowflake,有序遞增時間戳

雪花算法(Snowflake)更適用于需要自增的業(yè)務場景,如數據庫主鍵、訂單號、消息隊列的消息ID等, 時間戳一般是微秒級別,極限情況下,一微秒內可能同時多個請求進來導致重復。系統(tǒng)時鐘回撥時,UUID可能會重復,但是一般不會出現該情況,因此UUID這種方案的缺點可以接受,本案例使用UUID方案。

/**
 * 全局鏈路id生成工具類
 *
 * @author Ltx
 * @version 1.0
 */
public class RequestIdUtil {
    public RequestIdUtil() {
    }
    public static void setRequestId() {
        //往MDC中存入UUID唯一標識
        MDC.put(Constant.TRACE_ID, UUID.randomUUID().toString());
    }
    public static void setRequestId(String requestId) {
        MDC.put(Constant.TRACE_ID, requestId);
    }
    public static String getRequestId() {
        return MDC.get(Constant.TRACE_ID);
    }
    public static void clear() {
        //需要釋放,避免OOM
        MDC.clear();
    }
}
/**
 * Author:      liu_pc
 * Date:        2023/8/8
 * Description: 常量定義類
 * Version:     1.0
 */
public class Constant {
    /**
     * 全局唯一鏈路id
     */
    public final static String TRACE_ID = "traceId";
}

自定義全局唯一攔截器

Filter是Java Servlet 規(guī)范定義的一種過濾器接口,它的主要作用是在 Servlet 容器中對請求和響應進行攔截和處理,實現對請求和響應的預處理、后處理和轉換等功能。通過實現 Filter 接口,開發(fā)人員可以自定義一些過濾器來實現各種功能,如身份驗證、日志記錄、字符編碼轉換、防止 XSS 攻擊、防止 CSRF 攻擊等。那么這里我們使用它對請求做MDC賦值處理。

@Component
public class RequestIdFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
        try {
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            String requestId = httpServletRequest.getHeader("requestId");
            if (StringUtils.isBlank(requestId)) {
                RequestIdUtil.setRequestId();
            } else {
                RequestIdUtil.setRequestId(requestId);
            }
            // 繼續(xù)將請求傳遞給下一個過濾器或目標資源(比如Controller)
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            RequestIdUtil.clear();
        }
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }
    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}
    /**
     * 測試MDC異步任務全局鏈路
     *
     * @param param 請求參數
     * @return new String Info
     */
    public String test(String param) {
        logger.info("測試MDC test 接口開始,請求參數:{}", param);
        String requestId = RequestIdUtil.getRequestId();
        logger.info("MDC RequestId :{}", requestId);
        return "hello";
    }

日志配置

輸出到控制臺:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <!-- 配置輸出到控制臺(可選輸出到文件) -->
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <!-- 配置日志格式 -->
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %mdc %msg%n</pattern>
    </encoder>
  </appender>
  <!-- 配置根日志記錄器 -->
  <root level="INFO">
    <appender-ref ref="CONSOLE"/>
  </root>
  <!-- 配置MDC -->
  <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
    <resetJUL>true</resetJUL>
  </contextListener>
  <!-- 配置MDC插件 -->
  <conversionRule conversionWord="%mdc" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
</configuration>

輸出到文件:

<configuration>
    <!-- 配置輸出到文件 -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <!-- 指定日志文件路徑和文件名 -->
        <file>/Users/liu_pc/Documents/code/mdc_logback/logs/app.log</file>
        <encoder>
            <!-- 配置日志格式 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %mdc %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 配置根日志記錄器 -->
    <root level="INFO">
        <appender-ref ref="FILE"/>
    </root>
    <!-- 其他配置... -->
</configuration>

功能實現。

子線程獲取traceId問題

使用多線程時,子線程打印日志拿不到traceId。如果在子線程中獲取traceId,那么就相當于往各自線程中的MDC賦值了traceId,會導致子線程traceId不一致的問題。

    public void wrongHelloAsync(String param) {
        logger.info("helloAsync 開始執(zhí)行異步操作,請求參數:{}", param);
        List<Integer> simulateThreadList = new ArrayList<>(5);
        for (int i = 0; i <= 5; i++) {
            simulateThreadList.add(i);
        }
        for (Integer thread : simulateThreadList) {
            CompletableFuture.runAsync(() -> {
                //在子線程中賦值
                String requestId = RequestIdUtil.getRequestId();
                logger.info("子線程信息:{},traceId:{} ", thread, requestId);
            }, executor);
        }
    }
}

子線程獲取traceId方案

使用子線程時,可以使用自定義線程池重寫部分方法,在重寫的方法中獲取當前MDC數據副本,再將副本信息賦值給子線程的方案。

/**
 * Author:      liu_pc
 * Date:        2023/8/7
 * Description: 自定義異步線程池配置
 * Version:     1.0
 */
@Configuration
@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {
    private final Logger logger = LoggerFactory.getLogger(AsyncConfiguration.class);
    private final TaskExecutionProperties taskExecutionProperties;
    public AsyncConfiguration(TaskExecutionProperties taskExecutionProperties) {
        this.taskExecutionProperties = taskExecutionProperties;
    }
    @Override
    @Bean(name = "taskExecutor")
    public Executor initAsyncExecutor() {
        logger.debug("Creating Async Task Executor");
        ThreadPoolTaskExecutor executor = new MdcThreadPoolTaskExecutor();
        executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize());
        executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize());
        executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity());
        executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix());
        return executor;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}
/**
 * Author:      liu_pc
 * Date:        2023/8/7
 * Description: 自定義攜帶MDC信息線程池
 * Version:     1.0
 */
public class MdcThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
    @Override
    public void execute(@Nonnull Runnable task) {
        Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
        super.execute(
                () -> {
                    if (Objects.nonNull(copyOfContextMap)) {
                        String requestId = RequestIdUtil.getRequestId();
                        if (StringUtils.isBlank(requestId)) {
                            copyOfContextMap.put("traceId", UUID.randomUUID().toString());
                        }
                        //主線程MDC賦值子線程
                        MDC.setContextMap(copyOfContextMap);
                    } else {
                        RequestIdUtil.setRequestId();
                    }
                    try {
                        task.run();
                    } finally {
                        RequestIdUtil.clear();
                    }
                }
        );
    }
}

測試代碼:

    /**
     * 測試MDC異步任務全局鏈路
     *
     * @param param 請求參數
     * @return new String Info
     */
    public String test(String param) {
        logger.info("測試MDC test 接口開始,請求參數:{}", param);
        String requestId = RequestIdUtil.getRequestId();
        logger.info("MDC RequestId :{}", requestId);
        helloAsyncService.helloAsync(param, requestId);
        return "hello";
    }
    /**
     * 使用異步數據測試打印日志
     *
     * @param param     請求參數
     * @param requestId 全局唯一id
     */
    @Async("taskExecutor")
    public void helloAsync(String param, String requestId) {
        logger.info("helloAsync 開始執(zhí)行異步操作,請求參數:{}", param);
        List<Integer> simulateThreadList = new ArrayList<>(5);
        for (int i = 0; i <= 5; i++) {
            simulateThreadList.add(i);
        }
        for (Integer thread : simulateThreadList) {
            CompletableFuture.runAsync(() -> {
                //在子線程中賦值
                RequestIdUtil.setRequestId(requestId);
                logger.info("子線程信息:{},traceId:{} ", thread, requestId);
            }, executor);
        }
    }

MDC原理

到此這篇關于SpringBoot MDC全局鏈路解決方案的文章就介紹到這了,更多相關SpringBoot MDC全局鏈路內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringBoot集成ElaticJob定時器的實現代碼

    SpringBoot集成ElaticJob定時器的實現代碼

    這篇文章主要介紹了SpringBoot集成ElaticJob定時器的實現代碼,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-06-06
  • java實現MD5加密的方法小結

    java實現MD5加密的方法小結

    這篇文章主要介紹了java實現MD5加密的方法,結合具體實例形式總結分析了java實現md5加密的常用操作技巧與使用方法,需要的朋友可以參考下
    2017-10-10
  • idea類名顯示多行的設置方式

    idea類名顯示多行的設置方式

    在IntelliJ IDEA中,類名的顯示方式可以通過設置來調整,若想設置為單行顯示,需在設置中找到相關選項并勾選“√”,若需多行顯示,則取消勾選即可,此操作有助于優(yōu)化代碼視圖,提升開發(fā)效率
    2024-09-09
  • SpringBoot整合Shiro實現登錄認證的方法

    SpringBoot整合Shiro實現登錄認證的方法

    這篇文章主要介紹了SpringBoot整合Shiro實現登錄認證的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02
  • Java的IO流實現文件和文件夾的復制

    Java的IO流實現文件和文件夾的復制

    這篇文章主要為大家詳細介紹了Java的IO流實現文件和文件夾的復制,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • java 中如何獲取字節(jié)碼文件的相關內容

    java 中如何獲取字節(jié)碼文件的相關內容

    這篇文章主要介紹了java 中如何獲取字節(jié)碼文件的相關內容的相關資料,需要的朋友可以參考下
    2017-04-04
  • springboot解決Class path contains multiple SLF4J bindings問題

    springboot解決Class path contains multiple 

    這篇文章主要介紹了springboot解決Class path contains multiple SLF4J bindings問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Java8中l(wèi)ambda表達式的應用及一些泛型相關知識

    Java8中l(wèi)ambda表達式的應用及一些泛型相關知識

    這篇文章主要介紹了Java8中l(wèi)ambda表達式的應用及一些泛型相關知識的相關資料
    2017-01-01
  • 詳解java如何實現將數據導出為yaml

    詳解java如何實現將數據導出為yaml

    這篇文章主要為大家詳細介紹了java如何利用snakeyaml和freemarker實現將數據導出為yaml文件,文中的示例代碼講解詳細,有需要的小伙伴可以參考一下
    2023-11-11
  • Java與kotlin詳細對比

    Java與kotlin詳細對比

    這篇文章主要介紹了Java與kotlin詳細對比,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2021-09-09

最新評論