如何使用线程池的MDC?

在我们的软件中,我们广泛使用MDC来跟踪networking请求的会话ID和用户名等内容。 这在原始线程中运行正常。 但是,有很多事情需要在后台处理。 为此,我们使用java.concurrent.ThreadPoolExecutorjava.util.Timer类以及一些自卷asynchronous执行服务。 所有这些服务pipe理他们自己的线程池。

这就是Logback的手册在这样的环境中使用MDC的说法:

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

在这种情况下,build议在向执行程序提交任务之前,在原始(主)线程上调用MDC.getCopyOfContextMap()。 当任务运行时,作为其第一个操作,它应调用MDC.setContextMapValues()将原始MDC值的存储副本与新的Executorpipe理线程相关联。

这样可以,但是忘记添加这些电话是很容易的,而且要等到为时已晚,还没有简单的方法来识别问题。 Log4j的唯一标志就是在日志中丢失了MDC信息,而在Logback中,您会得到陈旧的MDC信息(因为在步骤池中的线程从在其上运行的第一个任务inheritance其MDC)。 两者都是生产系统中的严重问题。

我没有看到我们的情况有任何特殊之处,但我在网上找不到这个问题。 显然,这不是很多人碰到的事情,所以一定要有办法避免。 我们在这里做错了什么?

是的,这是我遇到的一个常见问题。 有几个解决方法(如手动设置,如上所述),但理想情况下,你需要一个解决scheme

  • 一致地设置MDC;
  • 避免MDC不正确的默认错误,但你不知道; 和
  • 最大限度地减less对线程池的使用方式的变化(例如,在MyCallable地方都使用MyCallable Callable进行子类化或类似的MyCallable )。

这是我使用的解决scheme,可以满足这三个需求。 代码应该是不言自明的。

(作为一个侧面说明,如果您使用番石榴的ListanableFuture ,则可以创build该执行者并将其提供给番石榴的MoreExecutors.listeningDecorator() 。)

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

我们遇到了类似的问题。 您可能需要扩展ThreadPoolExecutor并覆盖berfore / afterExecute方法,以便在启动/停止新线程之前调用所需的MDC。

与之前发布的解决scheme类似, RunnableCallablenewTaskFor方法可以被覆盖,以便在创buildRunnableFuture时包装参数(请参阅接受的解决scheme)。

注意:因此,必须调用executorServicesubmit方法,而不是execute方法。

对于ScheduledThreadPoolExecutordecorateTask方法会被覆盖。

我能用以下方法解决这个问题

在主线程(Application.java,我的应用程序的入口点)

 static public Map<String, String> mdcContextMap = MDC.getCopyOfContextMap(); 

在由Executer调用的类的运行方法中

 MDC.setContextMap(Application.mdcContextMap);