থ্রেড পুল সহ এমডিসি কীভাবে ব্যবহার করবেন?


146

আমাদের সফ্টওয়্যারটিতে আমরা ওয়েব অনুরোধগুলির জন্য সেশন আইডি এবং ব্যবহারকারীর নামগুলির মতো জিনিসগুলি ট্র্যাক করতে এমডিসি ব্যবহার করি। মূল থ্রেডে চলার সময় এটি সূক্ষ্মভাবে কাজ করে। তবে, পটভূমিতে প্রক্রিয়া করা দরকার এমন অনেকগুলি বিষয় রয়েছে। আমরা ব্যবহার java.concurrent.ThreadPoolExecutorএবং java.util.Timerকিছু স্ব-ঘূর্ণিত ASYNC সঞ্চালনের পরিষেবার সঙ্গে বরাবর ক্লাস। এই সমস্ত পরিষেবা তাদের নিজস্ব থ্রেড পুল পরিচালনা করে।

এই জাতীয় পরিবেশে এমডিসি ব্যবহার সম্পর্কে লগব্যাকের ম্যানুয়ালটিতে এটিই বলা হয়েছে:

ম্যাপযুক্ত ডায়াগনস্টিক প্রসঙ্গে একটি অনুলিপি সর্বদা আরম্ভের থ্রেড থেকে কর্মী থ্রেড দ্বারা উত্তরাধিকার সূত্রে প্রাপ্ত হতে পারে না। এটি যখন java.util.concurrent.Executors থ্রেড পরিচালনার জন্য ব্যবহৃত হয় তখনই এটি ঘটে। উদাহরণস্বরূপ, নতুনক্যাশডথ্রেডপুল পদ্ধতি একটি থ্রেডপুলএক্সেকিউটার তৈরি করে এবং অন্যান্য থ্রেড পুলিং কোডের মতো এটিতেও জটিল সূত্র তৈরির যুক্তি রয়েছে।

এই জাতীয় ক্ষেত্রে, সুপারিশ করা হয় যে MDC.getCopyOfContextMap () নির্বাহকের কাছে কোনও কার্য জমা দেওয়ার আগে মূল (মাস্টার) থ্রেডে ডাকা হবে। টাস্কটি যখন প্রথম ক্রিয়া হিসাবে চালিত হয়, তখন এটি MDC.setContextMapValues ​​() কে নতুন এক্সিকিউটার পরিচালিত থ্রেডের সাথে মূল MDC মানগুলির সঞ্চিত অনুলিপি সংযুক্ত করতে অনুরোধ জানানো উচিত।

এটি ঠিক আছে তবে এই কলগুলি যুক্ত করা ভুলে যাওয়া খুব সহজ এবং সমস্যাটি খুব দেরি না হওয়া পর্যন্ত সনাক্ত করার সহজ উপায় নেই। লগ 4 জের সাথে একমাত্র সাইন হ'ল আপনি লগগুলিতে এমডিসি তথ্য হারিয়েছেন, এবং লগব্যাকের সাহায্যে আপনি বাসি MDC তথ্য পাবেন (যেহেতু ট্রেড পুলের থ্রেডটি প্রথমে চালানো প্রথম কাজ থেকে এটির MDC উত্তরাধিকার সূত্রে প্রাপ্ত)। উভয়ই একটি উত্পাদন ব্যবস্থায় গুরুতর সমস্যা।

আমি আমাদের পরিস্থিতি কোনওভাবেই বিশেষ দেখতে পাচ্ছি না, তবুও ওয়েবে এই সমস্যাটি সম্পর্কে আমি খুব বেশি খুঁজে পাই না। স্পষ্টতই, এটি এমন কিছু নয় যার বিরুদ্ধে অনেকেই ধাক্কা খায়, সুতরাং এটি এড়াতে অবশ্যই একটি উপায় থাকতে হবে। আমরা এখানে কি ভুল করছি?


1
যদি আপনার অ্যাপ্লিকেশনটি জেইই পরিবেশের অধীনে মোতায়েন করা হয় তবে আপনি ইজেবি আবেদনের আগে এমডিসি প্রসঙ্গ নির্ধারণের জন্য জাভা ইন্টারসেপ্টর ব্যবহার করতে পারেন।
ম্যাক্সিম কিরিলভ

2
লগব্যাক সংস্করণ ১.১.৫ অনুসারে, এমডিসি মানগুলি আর চাইল্ড থ্রেড দ্বারা উত্তরাধিকার সূত্রে প্রাপ্ত হয় না।
Ceci

jira.qos.ch/browse/LOGBACK-422 সমাধান হয়েছে
লিজ্যাকাল

2
@ কেকি ডকুমেন্টেশনটি আপডেট করা দরকার: "একটি শিশু থ্রেড স্বয়ংক্রিয়ভাবে তার পিতামাতার ম্যাপযুক্ত ডায়াগনস্টিক প্রসঙ্গে একটি অনুলিপি পেয়ে যায়।" logback.qos.ch/manual/mdc.html
স্টেফেন

আমি slf4j এর জন্য একটি টান অনুরোধ তৈরি করেছি যা থ্রেড জুড়ে এমডিসি ব্যবহারের বিষয়টি সমাধান করে (লিঙ্ক github.com/qos-ch/slf4j/pull/150 )। হতে পারে, লোকেরা যদি মন্তব্য করে এবং এর জন্য জিজ্ঞাসা করে, তারা এসএলএফ 4 জেতে পরিবর্তনটি যুক্ত করবে :)
পুরুষ

উত্তর:


79

হ্যাঁ, এটি একটি সাধারণ সমস্যা যা আমিও চালিয়ে এসেছি। কয়েকটি কাজের ক্ষেত্র রয়েছে (যেমন বর্ণনা হিসাবে ম্যানুয়ালি সেট করা) তবে আদর্শিকভাবে আপনি একটি সমাধান চান যা এটি

  • ধারাবাহিকভাবে এমডিসি সেট করে;
  • এমডিসি ভুল যেখানে সারণী বাগগুলি এড়ানো যায় তবে আপনি এটি জানেন না; এবং
  • কিভাবে আপনি থ্রেড পুল ব্যবহার করতে ছোট পরিবর্তন (যেমন subclassing 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
রাইট; এটি সেট করা উচিত নয়। এটি কেবল ভাল হাইজিনের মতো বলে মনে হয়, যেমন যদি মোড়ক () পদ্ধতিটি রাস্তায় নামানো এবং অন্য কেউ ব্যবহার করেন।
jlevy

আপনি এই এমডিসিথ্রেডপুলএক্সেক্টরটি কীভাবে লোগ 4 জ 2 দ্বারা সংযুক্ত বা রেফারেন্স করেছিলেন সে সম্পর্কে একটি রেফারেন্স সরবরাহ করতে পারেন? কোথাও কোথাও আছে যেখানে আমাদের এই শ্রেণিটি বিশেষভাবে উল্লেখ করতে হবে, বা এটি "স্বয়ংক্রিয়ভাবে" সম্পন্ন হয়েছে? আমি পেয়ারা ব্যবহার করছি না আমি পারতাম, তবে আমি এটি ব্যবহারের আগে অন্য কোনও উপায় আছে কিনা তা জানতে চাই।
জেসিবি

আমি যদি আপনার প্রশ্নটি সঠিকভাবে বুঝতে পারি তবে উত্তরটি হ্যাঁ, এটি "ম্যাজিক" থ্রেড-লোকাল ভেরিয়েবলগুলি এসএলএফ 4 জে - এমডিসি.সেটকন্টেক্সটম্যাপ () ইত্যাদির প্রয়োগগুলি দেখুন Also এছাড়াও, উপায় দ্বারা, এটি লগ 4 জে নয়, এসএলএফ 4 জে ব্যবহার করে, যা ভাল is যেমন এটি Log4j, লগব্যাক এবং অন্যান্য লগিং সেটআপগুলির সাথে কাজ করে।
jlevy

1
কেবল সম্পূর্ণতার জন্য: আপনি যদি ThreadPoolTaskExecutorসরল জাভার পরিবর্তে স্প্রিংস ব্যবহার করছেন তবে আপনি moelholm.com/2017/07/24/… এ বর্ণিত ThreadPoolExecutorব্যবহার করতে পারেনMdcTaskDecorator
পিনো

27

আমরাও একই ধরণের সমস্যায় পড়েছি। আপনি নতুন থ্রেড শুরু / থামানোর আগে আপনার প্রয়োজন এমডিসি কলগুলি করার জন্য থ্রেডপুলএক্সেকিউটারকে প্রসারিত করতে এবং পরে / পরে এক্সেকিউট পদ্ধতিগুলির ওভাররাইড করতে চাইতে পারেন।


10
পদ্ধতিগুলি beforeExecute(Thread, Runnable)এবং afterExecute(Runnable, Throwable)অন্যান্য ক্ষেত্রে সহায়ক হতে পারে তবে এমডিসি সেট করার জন্য এটি কীভাবে কাজ করবে তা আমি নিশ্চিত নই। এগুলি উভয়ই প্রসারিত থ্রেডের অধীনে কার্যকর করা হয়। এর অর্থ আপনার আগে মূল থ্রেড থেকে আপডেট হওয়া মানচিত্রটি ধরে রাখতে সক্ষম হওয়া দরকার beforeExecute
কেনস্টন চোই

ফিল্টারে এমডিসি সেট করা আরও ভাল, এর অর্থ যখন ব্যবসায় যুক্তি দ্বারা প্রক্রিয়াধীন হয়, প্রসঙ্গটি আপডেট করা হবে না। আমি মনে করি না যে অ্যাপ্লিকেশন জুড়ে আমাদের
যেকোন

15

আইএমএইচও সর্বোত্তম সমাধান হ'ল:

  • ব্যবহার 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 সাহায্যে টাস্কডেকরেটর পদ্ধতির সাহায্যে টাস্কগুলি সজ্জিত করতে সক্ষম হন ot এটি কীভাবে করবেন তার একটি নমুনা এখানে সরবরাহ করা হয়েছে: 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

আমি নিম্নলিখিত পদ্ধতির সাহায্যে এটি সমাধান করতে সক্ষম হয়েছি

মূল থ্রেডে (অ্যাপ্লিকেশন.জভা, আমার অ্যাপ্লিকেশনটির প্রবেশের পয়েন্ট)

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

ক্লাসের রান পদ্ধতিতে যা এক্সিকিউটার দ্বারা ডাকা হয়

MDC.setContextMap(Application.mdcContextMap);
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.