使用ThreadPools或Spring Async的Logback MDC

8

我想确定在使用可缓存的线程池或Spring Async注解时,MDC的线程安全性如何。

我有一个调用多个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);
    }
}

由于线程池重复使用已经创建的线程,并且MDC使用ThreadLocal上下文映射,因此我们是否可能会丢失或损坏存储在MDC中的值?如果是这样,可能发生这种情况的潜在场景是什么?

1个回答

4
我想说我们发现了一个看起来很像你描述的问题。虽然我还没有确凿的证据,但是从代码中可以看出将事件刷新到附加器所需的时间可能会导致在线程池清理例程运行后读取MDC,特别是因为我们正在将日志刷新到Kafka(网络I/O通常比系统上的任何操作慢得多)。他们使用了一种可继承的写时复制线程本地来存储MDC映射本身,但我不确定它对MDC.clear()会有什么反应。我们有很多非常短暂的并行任务,所以混入Kafka似乎很可能会导致竞争条件。
另一方面,将每个条目与地图一起存储似乎不容易扩展(可能会导致内存爆炸和GC抖动)。我目前正在寻找一个折中方案。

你找到解决方案了吗?我认为我们可能会遇到同样的问题... - Java_Waldi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接