6

我试图确定MDC使用 Cacheable ThreadPools 或 Spring 的 Async 注释时的线程安全性如何。

我有一个调用多个CompletableFuture<>并使用线程池执行它们的方法

@Async
public CompletableFuture<List> someMethod(String request) {
    try {
        MDC.put("request", request)
        MDC.put("loggable1", "loggable1");
        MDC.put("loggable2", "loggable2");
        log.info("Log Event");
    } finally {
        MDC.clear();
    }
}

来自 Logback 的 MDCAdapter 的相关部分

final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>();

public void put(String key, String val) throws IllegalArgumentException {
    if (key == null) {
        throw new IllegalArgumentException("key cannot be null");
    }

    Map<String, String> oldMap = copyOnThreadLocal.get();
    Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);

    if (wasLastOpReadOrNull(lastOp) || oldMap == null) {
        Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
        newMap.put(key, val);
    } else {
        oldMap.put(key, val);
    }
}

public void clear() {
    lastOperation.set(WRITE_OPERATION);
    copyOnThreadLocal.remove();
}


public void remove(String key) {
    if (key == null) {
        return;
    }
    Map<String, String> oldMap = copyOnThreadLocal.get();
    if (oldMap == null)
        return;

    Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);

    if (wasLastOpReadOrNull(lastOp)) {
        Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
        newMap.remove(key);
    } else {
        oldMap.remove(key);
    }
}

由于 ThreadPools 重用了已经产生的线程,并且 MDC 使用 ThreadLocal 上下文映射。我们是否有可能丢失或损坏存储在 MDC 中的值?如果是这样,可能发生这种情况的潜在情况是什么?

4

1 回答 1

4

我只是想说,我们看到的东西看起来像你所描述的那样可疑。我还没有具体的证据,但是通过代码爬取看起来将事件刷新到附加程序所需的时间可能会导致在我们的线程池清理例程运行后读取 MDC,特别是因为我们正在刷新日志到 Kafka(网络 I/O 通常比系统上的任何东西都慢得多)。他们使用本地写入时复制可继承线程来存储 MDC 映射本身,但我不确定这将如何响应 MDC.clear() 例如。我们有许多并行执行的非常短暂的任务,因此混合使用 Kafka 似乎可能会导致竞争条件。

另一面 - 为每个条目存储地图的副本 - 似乎它可能无法扩展(可能导致内存爆炸和 GC 崩溃)。我目前正在寻找一个中间立场。

于 2019-05-07T20:06:53.917 回答