当社のソフトウェアでは、MDCCWeb リクエストのセッション ID やユーザー名などを追跡します。これは元のスレッドで実行している間は正常に機能します。
しかし、バックグラウンドで処理する必要があるものがたくさんあります。そのためには、クラスjava.concurrent.ThreadPoolExecutor
とjava.util.Timer
クラス、そしていくつかの自己ロールを使用します。非同期実行サービス。これらのサービスはすべて独自のスレッド プールを管理します。
これは何Logbackのマニュアルこのような環境で MDC を使用することについて、次のように述べています。
マップされた診断コンテキストのコピーは、必ずしも開始スレッドからワーカー スレッドに継承されるわけではありません。これは、スレッド管理に java.util.concurrent.Executors が使用される場合に当てはまります。たとえば、newCachedThreadPool メソッドは ThreadPoolExecutor を作成しますが、他のスレッド プーリング コードと同様に、複雑なスレッド作成ロジックがあります。
このような場合、タスクをエグゼキュータに送信する前に、元の (マスター) スレッドで MDC.getCopyOfContextMap() を呼び出すことをお勧めします。タスクが実行されると、最初のアクションとして MDC.setContextMapValues() を呼び出して、元の MDC 値の保存されたコピーを新しいエグゼキュータ管理スレッドに関連付ける必要があります。
これは問題ありませんが、これらの呼び出しを追加することを忘れるのは非常に簡単で、手遅れになるまで問題を認識する簡単な方法はありません。Log4j の場合の唯一の兆候は、ログに MDC 情報が欠落していることです。Logback の場合、古い MDC 情報が表示されます (スレッド プール内のスレッドは、その上で実行された最初のタスクから MDC を継承するため)。どちらも、実稼働システムでは深刻な問題です。
私たちの状況が特別だとは思いませんが、この問題について Web 上であまり情報を見つけることができませんでした。どうやら、これは多くの人が遭遇するものではないようですので、回避する方法があるはずです。私たちは何を間違っているのでしょうか?
ベストアンサー1
はい、これは私もよく遭遇する問題です。回避策はいくつかあります(説明したように手動で設定するなど)が、理想的には、
- MDC を一貫して設定します。
- MDCが間違っているが、それを知らないという暗黙のバグを回避します。
Callable
スレッド プールの使用方法の変更 (例:MyCallable
どこでもサブクラス化、または同様の醜悪な部分) を最小限に抑えます。
これら 3 つのニーズを満たす、私が使用しているソリューションを以下に示します。コードはわかりやすいものでなければなりません。
(補足として、MoreExecutors.listeningDecorator()
Guava の を使用する場合、このエグゼキュータを作成して Guava の に渡すことができます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);
}
}
}
};
}
}