थ्रेड पूल के साथ एमडीसी का उपयोग कैसे करें?


146

हमारे सॉफ्टवेयर में हम बड़े पैमाने पर एमडीसी का उपयोग सत्र आईडी और वेब अनुरोधों के लिए उपयोगकर्ता नाम जैसी चीजों को ट्रैक करने के लिए करते हैं। मूल धागे में चलने के दौरान यह ठीक काम करता है। हालाँकि, बहुत सारी चीजें हैं जिन्हें पृष्ठभूमि में संसाधित करने की आवश्यकता है। के लिए हम उपयोग करने वाले java.concurrent.ThreadPoolExecutorऔर java.util.Timerकुछ स्वयं लुढ़का async निष्पादन सेवाओं के साथ कक्षाएं। ये सभी सेवाएँ अपने स्वयं के थ्रेड पूल का प्रबंधन करती हैं।

इस तरह के वातावरण में MDC का उपयोग करने के बारे में लॉबैक के मैनुअल का यही कहना है:

मैप किए गए डायग्नोस्टिक संदर्भ की एक प्रति हमेशा आरंभ करने वाले धागे से श्रमिक धागे से विरासत में नहीं मिल सकती है। यह मामला है जब java.util.concurrent.Executors का उपयोग थ्रेड प्रबंधन के लिए किया जाता है। उदाहरण के लिए, NewCachedThreadPool विधि एक ThreadPoolExecutor बनाता है और अन्य थ्रेड पूलिंग कोड की तरह, इसमें थ्रेड निर्माण तर्क तर्क है।

ऐसे मामलों में, यह अनुशंसा की जाती है कि MDC.getCopyOfContextMap () को निष्पादित करने वाले को कार्य सौंपने से पहले मूल (मास्टर) थ्रेड पर लगाया जाए। जब कार्य चलता है, तो इसकी पहली कार्रवाई के रूप में, इसे MDC.setContextMapValues ​​() को मूल MDC मानों की संग्रहीत प्रतिलिपि को नए एक्ज़ीक्यूटर प्रबंधित थ्रेड के साथ जोड़ने के लिए आमंत्रित करना चाहिए।

यह ठीक होगा, लेकिन उन कॉल को जोड़ना भूल जाना बहुत आसान है, और समस्या को पहचानने का कोई आसान तरीका नहीं है जब तक कि बहुत देर हो चुकी हो। Log4j के साथ एकमात्र संकेत यह है कि आपको लॉग में MDC की जानकारी गायब है, और Logback के साथ आपको बासी MDC जानकारी मिलती है (चूंकि चलने वाले पूल में धागा उसके एमडीसी को पहले कार्य से विरासत में मिला है जो उस पर चलाया गया था)। दोनों एक उत्पादन प्रणाली में गंभीर समस्याएं हैं।

मैं किसी भी तरह से हमारी स्थिति को विशेष नहीं देखता, फिर भी मैं वेब पर इस समस्या के बारे में ज्यादा नहीं जान पाया। जाहिर है, यह ऐसी चीज नहीं है, जिसके खिलाफ कई लोग टकराते हैं, इसलिए इससे बचने का एक तरीका होना चाहिए। हम यहां क्या गलत कर रहे हैं?


1
यदि आपका आवेदन जेईई पर्यावरण के तहत तैनात है तो आप ईजेबी आह्वान से पहले एमडीसी संदर्भ स्थापित करने के लिए जावा इंटरसेप्टर का उपयोग कर सकते हैं।
मैक्सिम किरिलोव

2
लॉगबैक संस्करण 1.1.5 के रूप में, एमडीसी मूल्य अब बच्चे धागे द्वारा विरासत में नहीं मिले हैं।
सेकी


2
@Ceki प्रलेखन को अद्यतन करने की आवश्यकता है: "एक बच्चा धागा अपने माता-पिता के मैप किए गए नैदानिक ​​संदर्भ की एक प्रति स्वचालित रूप से प्राप्त करता है।" logback.qos.ch/manual/mdc.html
steffen

मैंने slf4j के लिए एक पुल अनुरोध बनाया है जो थ्रेड्स में एमडीसी (लिंक github.com/qos-ch/slf4j/pull/150 ) का उपयोग करने के मुद्दे को हल करता है । हो सकता है, अगर लोग टिप्पणी करते हैं और इसके लिए पूछते हैं, तो वे एसएलएफ 4 जे में बदलाव को शामिल करेंगे :)
पुरुष

जवाबों:


79

हां, यह एक सामान्य समस्या है जिसे मैंने भी चलाया है। कुछ वर्कअराउंड हैं (जैसे कि इसे मैन्युअल रूप से सेट करना, जैसा कि वर्णित है), लेकिन आदर्श रूप से आप एक समाधान चाहते हैं

  • एमडीसी को लगातार सेट करता है;
  • एबीसी टॉसिट बग से बचता है जहां एमडीसी गलत है लेकिन आप इसे नहीं जानते हैं; तथा
  • थ्रेड पूल (जैसे हर जगह, या समान कुरूपता के Callableसाथ उपवर्ग MyCallable) का उपयोग करने के तरीके में परिवर्तन को कम करता है ।

यहां एक समाधान है जो मैं उपयोग करता हूं जो इन तीन आवश्यकताओं को पूरा करता है। कोड स्व-व्याख्यात्मक होना चाहिए।

(एक साइड नोट के रूप में, यह निष्पादक बनाया और अमरूद को खिलाया जा सकता है MoreExecutors.listeningDecorator(), यदि आप अमरूद का उपयोग करते हैं 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);
                    }
                }
            }
        };
    }
}

यदि पिछला संदर्भ खाली नहीं है, तो क्या यह हमेशा कचरा नहीं है? आप इसे चारों ओर क्यों ले जाते हैं?
djjeck

2
सही; इसे सेट नहीं किया जाना चाहिए। यह सिर्फ अच्छी स्वच्छता की तरह लगता है, उदाहरण के लिए यदि रैप () विधि का खुलासा किया गया और सड़क के नीचे किसी और द्वारा उपयोग किया गया।
जलेवी

क्या आप एक संदर्भ प्रदान कर सकते हैं कि यह MdcThreadPoolExecutor Log4J2 द्वारा कैसे संलग्न या संदर्भित किया गया था? क्या कहीं ऐसी जगह है जहाँ हमें विशेष रूप से इस वर्ग को संदर्भित करने की आवश्यकता है, या क्या यह "स्वचालित रूप से" किया गया है? मैं अमरूद का उपयोग नहीं कर रहा हूं। मैं कर सकता था, लेकिन मैं यह जानना चाहूंगा कि क्या इसका उपयोग करने से पहले कोई और तरीका है।
जेसीबी

यदि मैं आपके प्रश्न को सही ढंग से समझता हूं, तो इसका उत्तर हां है, यह SLF4J में "मैजिक" थ्रेड-लोकल वैरिएबल है - MDC.setContextMap () आदि के कार्यान्वयन को देखें। इसके अलावा, यह SLF4J का उपयोग करता है, लॉग 4 जे का नहीं, जो बेहतर है। क्योंकि यह Log4j, Logback और अन्य लॉगिंग सेटअप के साथ काम करता है।
जलेवी

1
पूर्णता के लिए: यदि आप ThreadPoolTaskExecutorसादे जावा के बजाय स्प्रिंग का उपयोग कर रहे हैं ThreadPoolExecutor, तो आप moelholm.com/2017/07/24/…MdcTaskDecorator पर वर्णित का उपयोग कर सकते हैं
Pino

27

हम एक समान समस्या में भाग गए हैं। MDC कॉल को नए थ्रेड को शुरू / बंद करने से पहले आपको थ्रेडपूल एक्सक्यूटर और ओवरराइड करने के तरीकों को आगे बढ़ाना चाहिए।


10
तरीकों beforeExecute(Thread, Runnable)और afterExecute(Runnable, Throwable)अन्य मामलों में सहायक हो सकता है लेकिन मुझे यकीन है कि यह कैसे MDCs स्थापित करने के लिए काम करेंगे नहीं हूँ। वे दोनों स्पॉन्ड थ्रेड के तहत निष्पादित होते हैं। इसका मतलब है कि आपको पहले मुख्य थ्रेड से अपडेट किए गए नक्शे को पकड़ने में सक्षम होना चाहिए beforeExecute
केनस्टन चोई

फ़िल्टर में MDCs सेट करने के लिए बेहतर है, इसका मतलब है कि जब व्यापार तर्क द्वारा प्रसंस्करण के तहत अनुरोध किया जाता है, तो संदर्भ अपडेट नहीं किया जाएगा। मुझे नहीं लगता कि हमें आवेदन भर में एमडीसी को हर जगह अपडेट करना चाहिए
डेरेक

15

IMHO सबसे अच्छा समाधान है:

  • उपयोग ThreadPoolTaskExecutor
  • अपना खुद का लागू करें TaskDecorator
  • इसका इस्तेमाल करें: executor.setTaskDecorator(new LoggingTaskDecorator());

डेकोरेटर इस तरह दिख सकता है:

private final class LoggingTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable task) {
        // web thread
        Map<String, String> webThreadContext = MDC.getCopyOfContextMap();
        return () -> {
            // work thread
            try {
                // TODO: is this thread safe?
                MDC.setContextMap(webThreadContext);
                task.run();
            } finally {
                MDC.clear();
            }
        };
    }

}

क्षमा करें, वास्तव में निश्चित नहीं कि आपका क्या मतलब है। अद्यतन: मुझे लगता है कि मैं अब देख रहा हूं, मेरे जवाब में सुधार करेगा।
टॉम माईस्क

6

यह है कि मैं इसे निश्चित थ्रेड पूल और निष्पादक के साथ कैसे करता हूं:

ExecutorService executor = Executors.newFixedThreadPool(4);
Map<String, String> mdcContextMap = MDC.getCopyOfContextMap();

सूत्रण भाग में:

executor.submit(() -> {
    MDC.setContextMap(mdcContextMap);
    // my stuff
});

2

पहले से तैनात समाधान की तरह, newTaskForके लिए तरीके Runnableऔर Callableआदेश तर्क रैप करने के लिए में ओवरराइट किया जा सकता (स्वीकृत समाधान देखें) जब बनाने RunnableFuture

नोट: नतीजतन, executorServiceकी submitविधि के बजाय बुलाया जाना चाहिए executeविधि।

इसके लिए ScheduledThreadPoolExecutor, decorateTaskइसके बजाय तरीकों को अधिलेखित कर दिया जाएगा।


2

यदि आप एक स्प्रिंग फ्रेमवर्क संबंधित वातावरण में इस समस्या का सामना करते हैं, जहां आप @Asyncएनोटेशन का उपयोग करके कार्य चलाते हैं, तो आप टास्कडेकोरेटर दृष्टिकोण का उपयोग करके कार्यों को सजाने में सक्षम हैं । इसे कैसे करना है, इसका एक नमूना यहां दिया गया है: https://moelholm.com/blog/2017/07/24/spring-43-using-a-taskdecorator-to-copy-mdc-data-to-async-threads

मुझे इस मुद्दे का सामना करना पड़ा और ऊपर के लेख ने मुझे इससे निपटने में मदद की, इसलिए मैं इसे यहां साझा कर रहा हूं।


0

मौजूदा उत्तरों के समान एक और भिन्नता इसे लागू करने ExecutorServiceऔर एक प्रतिनिधि को इसे पारित करने की अनुमति देना है। फिर जेनरिक का उपयोग करते हुए, यह तब भी वास्तविक प्रतिनिधि को उजागर कर सकता है जब कोई कुछ आंकड़े प्राप्त करना चाहता है (जब तक कोई अन्य संशोधन विधियों का उपयोग नहीं किया जाता है)।

संदर्भ कोड:

public class MDCExecutorService<D extends ExecutorService> implements ExecutorService {

    private final D delegate;

    public MDCExecutorService(D delegate) {
        this.delegate = delegate;
    }

    @Override
    public void shutdown() {
        delegate.shutdown();
    }

    @Override
    public List<Runnable> shutdownNow() {
        return delegate.shutdownNow();
    }

    @Override
    public boolean isShutdown() {
        return delegate.isShutdown();
    }

    @Override
    public boolean isTerminated() {
        return delegate.isTerminated();
    }

    @Override
    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        return delegate.awaitTermination(timeout, unit);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return delegate.submit(wrap(task));
    }

    @Override
    public <T> Future<T> submit(Runnable task, T result) {
        return delegate.submit(wrap(task), result);
    }

    @Override
    public Future<?> submit(Runnable task) {
        return delegate.submit(wrap(task));
    }

    @Override
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
        return delegate.invokeAll(wrapCollection(tasks));
    }

    @Override
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
        return delegate.invokeAll(wrapCollection(tasks), timeout, unit);
    }

    @Override
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
        return delegate.invokeAny(wrapCollection(tasks));
    }

    @Override
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        return delegate.invokeAny(wrapCollection(tasks), timeout, unit);
    }

    @Override
    public void execute(Runnable command) {
        delegate.execute(wrap(command));
    }

    public D getDelegate() {
        return delegate;
    }

    /* Copied from https://github.com/project-ncl/pnc/blob/master/common/src/main/java/org/jboss/pnc/common
    /concurrent/MDCWrappers.java */

    private static Runnable wrap(final Runnable runnable) {
        final Map<String, String> context = MDC.getCopyOfContextMap();
        return () -> {
            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);
                }
            }
        };
    }

    private static <T> Callable<T> wrap(final Callable<T> callable) {
        final Map<String, String> context = MDC.getCopyOfContextMap();
        return () -> {
            Map previous = MDC.getCopyOfContextMap();
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            try {
                return callable.call();
            } finally {
                if (previous == null) {
                    MDC.clear();
                } else {
                    MDC.setContextMap(previous);
                }
            }
        };
    }

    private static <T> Consumer<T> wrap(final Consumer<T> consumer) {
        final Map<String, String> context = MDC.getCopyOfContextMap();
        return (t) -> {
            Map previous = MDC.getCopyOfContextMap();
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            try {
                consumer.accept(t);
            } finally {
                if (previous == null) {
                    MDC.clear();
                } else {
                    MDC.setContextMap(previous);
                }
            }
        };
    }

    private static <T> Collection<Callable<T>> wrapCollection(Collection<? extends Callable<T>> tasks) {
        Collection<Callable<T>> wrapped = new ArrayList<>();
        for (Callable<T> task : tasks) {
            wrapped.add(wrap(task));
        }
        return wrapped;
    }
}

-3

मैं निम्नलिखित दृष्टिकोण का उपयोग करके इसे हल करने में सक्षम था

मुख्य सूत्र में (Application.java, मेरे आवेदन का प्रवेश बिंदु)

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

उस वर्ग की रन पद्धति में जिसे एक्सक्यूटर द्वारा बुलाया जाता है

MDC.setContextMap(Application.mdcContextMap);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.