如何在线程池中使用MDC?

在我们的软件中,我们广泛使用MDC来跟踪Web请求的内容,例如会话ID和用户名。在原始线程中运行时,这可以正常工作。但是,有很多事情需要在后台处理。为此,我们使用java.concurrent.ThreadPoolExecutor和java.util.Timer类以及一些自卷式异步执行服务。所有这些服务都管理自己的线程池。

这是Logback手册关于在这样的环境中使用MDC的内容:

映射的诊断上下文的副本不能始终由工作线程从发起线程继承。当java.util.concurrent.Executors用于线程管理时,就是这种情况。例如,newCachedThreadPool方法创建ThreadPoolExecutor,并且像其他线程池代码一样,它具有复杂的线程创建逻辑。

在这种情况下,建议在将任务提交给执行者之前,在原始(主)线程上调用MDC.getCopyOfContextMap()。当任务运行时,作为第一个动作,它应调用MDC.setContextMapValues()将原始MDC值的存储副本与新的Executor托管线程相关联。

这样做很好,但是忘记添加这些呼叫非常容易,并且直到太晚之前,都没有简便的方法来识别问题。Log4j的唯一标志是您在日志中丢失了MDC信息,而使用Logback则获得了陈旧的MDC信息(因为胎面池中的线程从运行它的第一个任务继承了它的MDC)。两者都是生产系统中的严重问题。

我没有以任何方式看到我们的处境,但在网络上找不到太多有关此问题的信息。显然,这并不是很多人反对的事情,因此必须有一种避免的方法。我们在做什么错呢?

回答:

的,这也是我遇到的常见问题。有一些解决方法(如所述手动设置),但理想情况下,您需要一种解决方案

一致地设置MDC;

避免MDC不正确但您不知道的默认错误;和

最小化线程池使用方式的更改(例如Callable,MyCallable随处可见的子类化或类似的丑陋情况)。

这是我使用的满足这三个需求的解决方案。代码应该是不言自明的。

(请注意,MoreExecutors.listeningDecorator()如果您使用Guava的,可以创建此执行程序并将其馈送到Guava的ListanableFuture。)

import org.slf4j.MDC;

import java.util.Map;

import java.util.concurrent.*;

/**

* A SLF4J MDC-compatible {@link ThreadPoolExecutor}.

* <p/>

* In general, MDC is used to store diagnostic information (e.g. a user's session id) in per-thread variables, to facilitate

* logging. However, although MDC data is passed to thread children, this doesn't work when threads are reused in a

* thread pool. This is a drop-in replacement for {@link ThreadPoolExecutor} sets MDC data before each task appropriately.

* <p/>

* Created by jlevy.

* Date: 6/14/13

*/

public class MdcThreadPoolExecutor extends ThreadPoolExecutor {

final private boolean useFixedContext;

final private Map<String, Object> fixedContext;

/**

* Pool where task threads take MDC from the submitting thread.

*/

public static MdcThreadPoolExecutor newWithInheritedMdc(int corePoolSize, int maximumPoolSize, long keepAliveTime,

TimeUnit unit, BlockingQueue<Runnable> workQueue) {

return new MdcThreadPoolExecutor(null, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);

}

/**

* Pool where task threads take fixed MDC from the thread that creates the pool.

*/

@SuppressWarnings("unchecked")

public static MdcThreadPoolExecutor newWithCurrentMdc(int corePoolSize, int maximumPoolSize, long keepAliveTime,

TimeUnit unit, BlockingQueue<Runnable> workQueue) {

return new MdcThreadPoolExecutor(MDC.getCopyOfContextMap(), corePoolSize, maximumPoolSize, keepAliveTime, unit,

workQueue);

}

/**

* Pool where task threads always have a specified, fixed MDC.

*/

public static MdcThreadPoolExecutor newWithFixedMdc(Map<String, Object> fixedContext, int corePoolSize,

int maximumPoolSize, long keepAliveTime, TimeUnit unit,

BlockingQueue<Runnable> workQueue) {

return new MdcThreadPoolExecutor(fixedContext, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);

}

private MdcThreadPoolExecutor(Map<String, Object> fixedContext, int corePoolSize, int maximumPoolSize,

long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {

super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);

this.fixedContext = fixedContext;

useFixedContext = (fixedContext != null);

}

@SuppressWarnings("unchecked")

private Map<String, Object> getContextForTask() {

return useFixedContext ? fixedContext : MDC.getCopyOfContextMap();

}

/**

* All executions will have MDC injected. {@code ThreadPoolExecutor}'s submission methods ({@code submit()} etc.)

* all delegate to this.

*/

@Override

public void execute(Runnable command) {

super.execute(wrap(command, getContextForTask()));

}

public static Runnable wrap(final Runnable runnable, final Map<String, Object> context) {

return new Runnable() {

@Override

public void run() {

Map previous = MDC.getCopyOfContextMap();

if (context == null) {

MDC.clear();

} else {

MDC.setContextMap(context);

}

try {

runnable.run();

} finally {

if (previous == null) {

MDC.clear();

} else {

MDC.setContextMap(previous);

}

}

}

};

}

}

以上是 如何在线程池中使用MDC? 的全部内容, 来源链接: utcz.com/qa/417928.html

回到顶部