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);
    }
}

 

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