Как использовать MDC с parallelStream в Java и logback - PullRequest
0 голосов
/ 20 декабря 2018

Мне нужно зарегистрировать несколько атрибутов запроса, таких как идентификатор запроса и локаль, но при использовании parallelStream кажется, что ThreadLocal MDC теряет информацию.

Я проанализировал решение о передаче контекста MDC между потоками при создании ParallelsStream, но он выглядит грязным, и у меня также есть много применений ParallelsStream.

Есть ли другой способделать это?

Спасибо

1 Ответ

0 голосов
/ 03 сентября 2019

Единственное решение, которое я нашел, - это скопировать контекст в конечную переменную вне потока и применить его для каждой отдельной итерации:

Map<String, String> contextMap = MDC.getCopyOfContextMap();
Stream.iterate(0, i -> i + 1).parallel()
    .peek(i -> MDC.setContextMap(contextMap))
    // ...logic...
    // in case you're using a filter, you need to use a predicate and combine it with a clear step:
    filter(yourPredicate.or(i -> {
                MDC.clear();
                return false;
            }))
    // clear right before terminal operation
    .peek(i -> MDC.clear())
    .findFirst();

// since the initial thread is also used within the stream and the context is cleared there, 
// we need to set it again to its initial state
MDC.setContextMap(contextMap);    

Стоимость этого решения составляет 1) несколько микросекундна 100 итераций и 2) хуже читаемость, но оба приемлемы:

  1. Это тест, сравнивающий IntStream.range(0, 100).parallel().sum() (= базовый уровень) с тем же потоком, который использует эту логику копирования MDC:
Benchmark               Mode  Cnt   Score   Error   Units
MDC_CopyTest.baseline  thrpt    5   0,038 ± 0,005  ops/us
MDC_CopyTest.withMdc   thrpt    5   0,024 ± 0,001  ops/us
MDC_CopyTest.baseline   avgt    5  28,239 ± 1,308   us/op
MDC_CopyTest.withMdc    avgt    5  40,178 ± 0,761   us/op
Чтобы улучшить читабельность, его можно заключить в небольшой вспомогательный класс:
public class MDCCopyHelper {
    private Map<String, String> contextMap = MDC.getCopyOfContextMap();

    public void set(Object... any) {
        MDC.setContextMap(contextMap);
    }

    public void clear(Object... any) {
        MDC.clear();
    }

    public boolean clearAndFail() {
        MDC.clear();
        return false;
    }
}

Потоковый код выглядит тогда немного лучше:

MDCCopyHelper mdcHelper = new MDCCopyHelper();
Optional<Integer> findFirst = Stream.iterate(0, i -> i + 1)
        .parallel()
        .peek(mdcHelper::set)
        // ...logic...
        // filters predicates should be combined with clear step
        .filter(yourPredicate.or(mdcHelper::clearAndFail))
        // before terminal call:
        .peek(mdcHelper::clear)
        .findFirst();
mdcHelper.set();
...