CompletableFuture是否具有相应的本地上下文?

时间:2016-06-21 00:11:10

标签: java-8 logback completable-future

在过去的日子里,我们有ThreadLocal用于程序携带数据和请求路径,因为所有请求处理都是在该线程上完成的,而像logback这样的东西用于MDC.put(“requestId”,getNewRequestId());

然后scala和函数式编程出现了,期货出现了,随之而来的是Local.scala(至少我知道twitter期货有这个类)。 Future.scala知道Local.scala并通过所有map / flatMap等等传递上下文功能,这样我仍然可以做Local.set(“requestId”,getNewRequestId());在经过多个线程之后,我仍然可以使用Local.get(...)

访问它

Soooo,我的问题是在java中,我可以使用LocalContext或某个对象(不确定名称)使用新的CompletableFuture做同样的事情,这样,我可以修改logback MDC上下文来存储它上下文而不是ThreadLocal,这样我就不会丢失请求ID,所有我的日志都会通过thenApply,thenAccept等等仍然可以正常工作,并且在logback配置中记录和-XrequestId标志

编辑:

举个例子。如果您有请求进入并且您正在使用log4j或logback,则在过滤器中,您将设置MDC.put(“requestId”,requestId),然后在您的应用程序中,您将记录许多日志语句

log.info("request came in for url="+url);
log.info("request is complete");

现在,在日志输出中它将显示

INFO {time}: requestId425 request came in for url=/mypath
INFO {time}: requestId425 request is complete

这是使用ThreadLocal的技巧来实现这一点。在twitter上,我们在scala中使用scala和twitter Futures以及Local.scala类。 Local.scala和Future.scala捆绑在一起,因为我们可以实现上面的场景仍然很好,我们所有的日志语句都可以记录请求ID,所以开发人员永远不必记住记录请求ID,你可以追踪单个客户请求具有该ID的响应周期。

我在java中没有看到这个:(这是非常不幸的,因为有很多用例。也许有些东西我看不到?

2 个答案:

答案 0 :(得分:1)

我的解决方案主题是(它将与JDK 9+一起使用,因为从该版本开始就公开了几个可重写的方法)

  

让完整的生态系统了解MDC

为此,我们需要解决以下情况:

  • 何时从此类中获得所有CompletableFuture的新实例?→我们需要返回一个MDC感知的相同版本。
  • 什么时候都能从此类之外获得CompletableFuture的新实例?→我们需要返回一个MDC感知的相同版本。
  • 在CompletableFuture类中使用哪个执行程序?→在所有情况下,我们需要确保所有执行程序都知道MDC

为此,我们通过扩展它来创建CompletableFuture的MDC感知版本类。我的版本如下所示

import org.slf4j.MDC;

import java.util.Map;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.function.Supplier;

public class MDCAwareCompletableFuture<T> extends CompletableFuture<T> {

    public static final ExecutorService MDC_AWARE_ASYNC_POOL = new MDCAwareForkJoinPool();

    @Override
    public CompletableFuture newIncompleteFuture() {
        return new MDCAwareCompletableFuture();
    }

    @Override
    public Executor defaultExecutor() {
        return MDC_AWARE_ASYNC_POOL;
    }

    public static <T> CompletionStage<T> getMDCAwareCompletionStage(CompletableFuture<T> future) {
        return new MDCAwareCompletableFuture<>()
                .completeAsync(() -> null)
                .thenCombineAsync(future, (aVoid, value) -> value);
    }

    public static <T> CompletionStage<T> getMDCHandledCompletionStage(CompletableFuture<T> future,
                                                                Function<Throwable, T> throwableFunction) {
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
        return getMDCAwareCompletionStage(future)
                .handle((value, throwable) -> {
                    setMDCContext(contextMap);
                    if (throwable != null) {
                        return throwableFunction.apply(throwable);
                    }
                    return value;
                });
    }
}

MDCAwareForkJoinPool类看起来像

public class MDCAwareForkJoinPool extends ForkJoinPool {
    //Override constructors which you need

    @Override
    public <T> ForkJoinTask<T> submit(Callable<T> task) {
        return super.submit(MDCUtility.wrapWithMdcContext(task));
    }

    @Override
    public <T> ForkJoinTask<T> submit(Runnable task, T result) {
        return super.submit(wrapWithMdcContext(task), result);
    }

    @Override
    public ForkJoinTask<?> submit(Runnable task) {
        return super.submit(wrapWithMdcContext(task));
    }

    @Override
    public void execute(Runnable task) {
        super.execute(wrapWithMdcContext(task));
    }
}

要包装的实用方法例如

public static <T> Callable<T> wrapWithMdcContext(Callable<T> task) {
    //save the current MDC context
    Map<String, String> contextMap = MDC.getCopyOfContextMap();
    return () -> {
        setMDCContext(contextMap);
        try {
            return task.call();
        } finally {
            // once the task is complete, clear MDC
            MDC.clear();
        }
    };
}

public static Runnable wrapWithMdcContext(Runnable task) {
    //save the current MDC context
    Map<String, String> contextMap = MDC.getCopyOfContextMap();
    return () -> {
        setMDCContext(contextMap);
        try {
            return task.run();
        } finally {
            // once the task is complete, clear MDC
            MDC.clear();
        }
    };
}

public static void setMDCContext(Map<String, String> contextMap) {
   MDC.clear();
   if (contextMap != null) {
       MDC.setContextMap(contextMap);
    }
}

以下是一些使用准则:

  • 使用类MDCAwareCompletableFuture而不是类CompletableFuture
  • CompletableFuture中的几个方法实例化自身版本,例如new CompletableFuture...。对于此类方法(大多数公共静态方法),请使用替代方法来获取MDCAwareCompletableFuture的实例。使用替代方法的一个例子可能是使用CompletableFuture.supplyAsync(...)
  • ,而不是使用new MDCAwareCompletableFuture<>().completeAsync(...)
  • 当您因为某个外部库而返回CompletableFuture实例而陷入困境时,使用方法MDCAwareCompletableFuturegetMDCAwareCompletionStage的实例转换为CompletableFutureMDCAwareForkJoinPool 。显然,您无法在该库中保留上下文,但是在您的代码触及应用程序代码后,此方法仍将保留上下文。
  • 在提供执行程序作为参数时,请确保它是MDC Aware,例如MDCAwareThreadPoolExecutor。您也可以通过覆盖execute方法来创建public class testArrayList { public static void main(String[] args) { ArrayList<String> auto = new ArrayList<String>(); auto.add("MITSUBISHI"); auto.add("Hyundae"); auto.add("Ford"); auto.add("Ferrari"); auto.add("Mazda"); auto.add("Mustang"); auto.add("Lamborghini"); for(String cars : auto) { System.out.println(cars); } } } ,以服务您的用例。你明白了!

您可以在post中以相同的方式找到以上所有内容的详细说明

答案 1 :(得分:0)

如果遇到此问题,只需在此处戳线程 http://mail.openjdk.java.net/pipermail/core-libs-dev/2017-May/047867.html

实现类似于twitter Futures之类的东西,该东西传递Locals(与ThreadLocal类似,但传递状态)。

请参阅此处的def response()方法及其如何调用Locals.save()和Locals.restort() https://github.com/simonratner/twitter-util/blob/master/util-core/src/main/scala/com/twitter/util/Future.scala

如果Java作者可以解决此问题,则logback中的MDC将可在所有第3方库中使用。在此之前,除非您可以更改第3方库(否则您可以这样做),否则它将不起作用。