log4j MDC導致的程序卡死

有這麼一段代碼,

public class JobLogUtil {
    private static Logger logger = Logger.getLogger("SYSTEM");

 /**
 * 消息級別
 * @param modelType
 * @param node
 * @param logType
 * @param jobId
 * @param organizationId
 * @param message
 * @param remark
 */
public static void info(Integer modelType, String node, Integer logType, Integer jobId, Long organizationId, String message, String remark) {
    message=message.replace("'","\\'");
    MDC.put("modelType", modelType);

    if (StringUtils.isBlank(node)) {
        MDC.put("node", "");
    } else {
        MDC.put("node", node);
    }

    MDC.put("logType", logType);

    if (null == jobId) {
        MDC.put("jobId", 0);
    } else {
        MDC.put("jobId", jobId);
    }

    if (null == organizationId) {
        MDC.put("organizationId", 0);
    } else {
        MDC.put("organizationId", organizationId);
    }

    MDC.put("message", message);

    if (StringUtils.isBlank(remark)) {
        MDC.put("remark", "");
    } else {
        MDC.put("remark", remark);
    }
    logger.info(message);
}}

調用之後,整個程序卡住無法繼續執行,非常奇怪,在本地測試沒發現什麼問題,但是在服務器上就卡死了,或者執行非常非常慢。

描述下整個流程, 執行某些任務,因爲任務的量比較大,使用谷歌的

ListeningExecutorService executorService = MoreExecutors.listeningDecorator(threadPoolExecutor);

構建了線程池,然後將任務全部提交到線程池中,

futures.add(executorService.submit(new Task())

執行完成後,

ListenableFuture<List<Task>> resultsFuture = Futures.successfulAsList(futures);

獲取線程執行的結果。

阻塞就發生在Task 子線程任務中,爲什麼會發生卡死的問題?

首先來說下MDC是什麼

 MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日誌的功能。某些應用程序採用多線程的方式來處理多個用戶的請求。在一個用戶的使用過程中,可能有多個不同的線程來進行處理。典型的例子是 Web 應用服務器。當用戶訪問某個頁面時,應用服務器可能會創建一個新的線程來處理該請求,也可能從線程池中複用已有的線程。在一個用戶的會話存續期間,可能有多個線程處理過該用戶的請求。這使得比較難以區分不同用戶所對應的日誌。當需要追蹤某個用戶在系統中的相關日誌記錄時,就會變得很麻煩。

MDC 可以看成是一個與當前線程綁定的哈希表,可以往其中添加鍵值對。MDC 中包含的內容可以被同一線程中執行的代碼所訪問。當前線程的子線程會繼承其父線程中的 MDC 的內容。當需要記錄日誌時,只需要從 MDC 中獲取所需的信息即可。MDC 的內容則由程序在適當的時候保存進去。對於一個 Web 應用來說,通常是在請求被處理的最開始保存這些數據。

 

看到上面大概就有一些想法了,MDC用到了ThreadLocal和哈希表map,這2個東西在多線程的環境下是比較常用的,但一旦用的不好,就是造成死鎖,循環等等問題,特別是使用完後應該及時清理

Map map = MDC.getContext();  
        if(map != null) {  
            map.clear();  
        }  

將上面MDC代碼塊去掉後,程序正常執行,說明就是MDC使用不當造成的問題。

在多線程環境下,儘量不要使用自己不熟悉的技術,以免造成問題而不知。

 

2021年12月17日14:43:58 補充

MDC採用Map的方式存儲上下文,線程獨立的,子線程會從父線程拷貝上下文。其調用方法以下:

  • 保存信息到上下文

    MDC.put(key, value);

  • 從上下文獲取設置的信息

    MDC.get(key);

  • 清楚上下文中指定的key的信息

    MDC.remove(key);

  • 清除全部

    clear();

  • 輸出模板,注意是大寫 [%X{key}]

    log4j.appender.consoleAppender.layout.ConversionPattern = %-4r [%t] %5p %c %x - %m - %X{key}%n

log4j 1.xMDC 的使用方式如下:

 @Override
 public void doFilter(ServletRequest request, ServletResponse response, 
     FilterChain chain) throws IOException, ServletException {
     try {
         // 填充數據
         MDC.put(Contents.REQUEST_ID, UUID.randomUUID().toString());
         chain.doFilter(request, response);
     } finally {
         // 請求結束時清除數據,否則會造成內存泄露問題
        MDC.remove(Contents.REQUEST_ID);
    }
}

沒有釋放,最終導致出問題

log4j 2.x 中,使用 ThreadContext 代替了 MDCNDC,使用方式如下:

 @Override
 public void doFilter(ServletRequest request, ServletResponse response, 
     FilterChain chain) throws IOException, ServletException {
     try {
         // 填充數據
         ThreadContext.put(Contents.REQUEST_ID, UUID.randomUUID().toString());
         chain.doFilter(request, response);
     } finally {
         // 請求結束時清除數據,否則會造成內存泄露問題
        ThreadContext.remove(Contents.REQUEST_ID);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章