200 votes

Comment utiliser MDC avec des pools de threads?

Dans notre logiciel, nous avons beaucoup utiliser les MDC suivre les choses comme Id de session et les noms d'utilisateur pour les requêtes web. Cela fonctionne bien lors de l'exécution dans le thread d'origine. Cependant, il y a beaucoup de choses qui doivent être traitées en arrière-plan. Pour cela nous utilisons l' java.concurrent.ThreadPoolExecutor et java.util.Timer les classes avec quelques roulées async services d'exécution. Tous ces services gèrent leur propre pool de threads.

C'est ce que Logback manuel a à dire sur l'utilisation de MDC dans un tel environnement:

Une copie de la mappé contexte diagnostique ne peut pas toujours être héritées par les threads de l'ouverture de la thread. C'est le cas de java.util.de façon concomitante.Les exécuteurs testamentaires est utilisé pour la gestion des threads. Par exemple, newCachedThreadPool méthode crée un ThreadPoolExecutor et comme les autres fil groupement de code, il est complexe de création de thread logique.

Dans de tels cas, il est recommandé que le MDC.getCopyOfContextMap() est appelée sur l'original (master) thread avant de soumettre une tâche à l'exécuteur testamentaire. Lors de l'exécution de la tâche, en tant que sa première action, il doit invoquer le MDC.setContextMapValues() pour associer la copie de l'original MDC valeurs avec le nouvel Exécuteur testamentaire géré fil.

Ce serait bien, mais il est très facile d'oublier l'ajout de ces appels, et il n'est pas facile de reconnaître le problème jusqu'à ce qu'il soit trop tard. Le seul signe avec Log4j est que vous obtenez manquant MDC de l'info dans les journaux, et avec Logback vous obtenir rassis MDC info (depuis le fil dans la bande de roulement piscine hérite de sa MDC de la première tâche qui a été couru sur celui-ci). Les deux sont de graves problèmes dans un système de production.

Je ne vois pas notre situation particulière, en quelque sorte, mais je ne pouvais pas trouver beaucoup sur ce sujet sur le web. Apparemment, ce n'est pas quelque chose que beaucoup de gens se heurtent, donc il doit y avoir un moyen de l'éviter. Que faisons-nous du mal ici?

107voto

jlevy Points 141

Oui, ce est un problème commun que j'ai pu croiser. Il ya quelques solutions de contournement (comme réglant manuellement, comme décrit), mais, idéalement, vous voulez une solution

  • Définit le MDC de façon uniforme;
  • Évite tacite bugs où le MDC est incorrect, mais vous ne le savez pas; et
  • Minimise les modifications à la façon dont vous utilisez des pools de threads (par exemple, sous-classement Callable avec MyCallable partout, ou similaire, de la laideur).

Voici une solution que j'utilise qui répond à ces trois besoins. Le Code devrait être auto-explicatif.

(Comme une note de côté, ce exécuteur testamentaire peut être créé et nourri à la Goyave de l' MoreExecutors.listeningDecorator(), si vous utilisez la Goyave de l' 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);
                    }
                }
            }
        };
    }
}

28voto

Mark Points 109

Nous avons rencontré un problème similaire. Vous souhaiterez peut-être étendre à ThreadPoolExecutor le remplacement des méthodes avant / aprèsExecute pour établir les appels MDC dont vous avez besoin avant de démarrer / arrêter de nouveaux threads.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X