如何将 MDC 与线程池一起使用? [英] How to use MDC with thread pools?

查看:61
本文介绍了如何将 MDC 与线程池一起使用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我们的软件中,我们广泛使用 MDC 来跟踪Web 请求的会话 ID 和用户名之类的东西.这在原始线程中运行时效果很好.

In our software we extensively use MDC to track things like session IDs and user names for web requests. This works fine while running in the original thread.

但是,有很多事情需要在后台处理.为此,我们使用 java.concurrent.ThreadPoolExecutorjava.util.Timer 类以及一些自滚动的 async 执行服务.所有这些服务都管理自己的线程池.

However, there's a lot of things that need to be processed in the background. For that we use the java.concurrent.ThreadPoolExecutor and java.util.Timer classes along with some self-rolled async execution services. All these services manage their own thread pool.

这就是 Logback's manual 关于在这种情况下使用 MDC 的说法环境:

This is what Logback's manual has to say about using MDC in such an environment:

映射诊断上下文的副本不能总是由工作线程从启动线程继承.当 java.util.concurrent.Executors 用于线程管理时就是这种情况.比如newCachedThreadPool方法创建了一个ThreadPoolExecutor,和其他线程池代码一样,它有复杂的线程创建逻辑.

A copy of the mapped diagnostic context can not always be inherited by worker threads from the initiating thread. This is the case when java.util.concurrent.Executors is used for thread management. For instance, newCachedThreadPool method creates a ThreadPoolExecutor and like other thread pooling code, it has intricate thread creation logic.

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

In such cases, it is recommended that MDC.getCopyOfContextMap() is invoked on the original (master) thread before submitting a task to the executor. When the task runs, as its first action, it should invoke MDC.setContextMapValues() to associate the stored copy of the original MDC values with the new Executor managed thread.

这没问题,但是很容易忘记添加这些调用,并且没有简单的方法可以识别问题,直到为时已晚.Log4j 的唯一标志是您在日志中丢失了 MDC 信息,而使用 Logback 您会得到陈旧的 MDC 信息(因为线程池中的线程从在其上运行的第一个任务继承了它的 MDC).两者都是生产系统中的严重问题.

This would be fine, but it is a very easy to forget adding those calls, and there is no easy way to recognize the problem until it is too late. The only sign with Log4j is that you get missing MDC info in the logs, and with Logback you get stale MDC info (since the thread in the tread pool inherits its MDC from the first task that was ran on it). Both are serious problems in a production system.

我不认为我们的情况有任何特别之处,但我在网上找不到关于这个问题的太多信息.显然,这不是很多人会碰到的事情,所以必须有办法避免它.我们在这里做错了什么?

I don't see our situation special in any way, yet I could not find much about this problem on the web. Apparently, this is not something that many people bump up against, so there must be a way to avoid it. What are we doing wrong here?

推荐答案

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

Yes, this is a common problem I've run into as well. There are a few workarounds (like manually setting it, as described), but ideally you want a solution that

  • 一致地设置 MDC;
  • 避免 MDC 不正确但您不知道的隐性错误;和
  • 尽量减少对线程池使用方式的更改(例如,子类化 CallableMyCallable 无处不在,或类似的丑陋).
  • Sets the MDC consistently;
  • Avoids tacit bugs where the MDC is incorrect but you don't know it; and
  • Minimizes changes to how you use thread pools (e.g. subclassing Callable with MyCallable everywhere, or similar ugliness).

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

Here's a solution that I use that meets these three needs. Code should be self-explanatory.

(作为旁注,可以创建此执行程序并将其提供给 Guava 的 MoreExecutors.listeningDecorator(),如果你使用 Guava 的 ListanableFuture.)

(As a side note, this executor can be created and fed to Guava's MoreExecutors.listeningDecorator(), if you use Guava's 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 与线程池一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆