【问题标题】:Reactive mono how to propagate subscription context to `doOnSubscribe` and `doFinally`反应式单声道如何将订阅上下文传播到`doOnSubscribe`和`doFinally`
【发布时间】:2020-06-22 22:47:36
【问题描述】:

我有以下跟踪方法执行时间的方面:

public Object addMetricsToReactiveMonoTimedMethod(ProceedingJoinPoint pjp, ReactiveTimed reactiveTimed) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        Mono<?> mono = (Mono<?>) pjp.proceed();
        return mono
                .doOnSubscribe(subscription -> stopWatch.start())
                .doFinally(signalType -> {
                    stopWatch.stop();
                    logTimer(pjp, stopWatch, reactiveTimed.name(), signalType);
                });
}

方法本身是这样的:

public Mono<String> sayHi() { 
  return Mono.just("hi")
    .subscriberContext(context -> context.put("requestId", "requestId"));
}

如何在我的方面方法中从订阅者上下文中获取requestId 变量?我想在doFinally 中使用它来了解分析了哪个请求。

【问题讨论】:

  • 这只是一个杀手级的短语,因此不是很有建设性。为什么 AspectJ 阻塞的事实会成为一个问题?它与截获的代码在同一个线程中运行,只是用一些额外的功能(横切关注点)装饰它。那是合法的。这取决于开发人员不要在她的方面做不必要的“昂贵”(和阻塞)的东西。如果某个方面使整个应用程序脱轨,这就是一个问题——不管有没有反应式编程。使用强大的工具而不是“用工具做傻瓜”需要练习、经验和关心,就像在这个行业中一样。
  • @ThomasAndolf 您从哪里得知 AOP 阻塞了?这不是真的。将 AOP 与 Reactor 一起使用没有任何问题。问题中的代码完全没有问题,您可以在resilience4j中找到其他示例:github.com/resilience4j/resilience4j/blob/… 请停止传播虚假信息...
  • @IvanLymar 您可以尝试用于跟踪的 spring-cloud-sleuth 库。它能够基于 Reactor 上下文填充 MDC 日志上下文。因此,您也许可以在日志中获取请求 ID,而无需手动将其放入。

标签: reactive-programming aspectj spring-webflux project-reactor


【解决方案1】:

免责声明:我一生中从未使用过 Reactor 或类似的东西。由于 aspectj 标签,我发现了这个问题。

在我快速查看Mono Javadoc 之后,您似乎可以在方面再次调用subscriberContext(Function&lt;Context, Context&gt;),就像在目标方法中一样。您将现有上下文作为函数或 lambda 的输入参数,并可以根据需要使用它。您的虚拟函数/lambda 的结果将是一个新的上下文,但您可以丢弃它。我没有测试过,但我的意思是这样的:

// ...

Mono<?> mono = (Mono<?>) pjp.proceed();

// Alternatively, use a List<Context> with one element, a Stack<Context>, ...
Context[] targetContext = new Context[1];

mono.subscriberContext(context -> {
  targetContext[0] = context;
  // We can also return null, it does not matter because we are not interested
  // in the newly created context, only in the original one we salvaged into the
  // outer array.
  return context;
});

System.out.println("Now do whatever you need to do with " + targetContext[0]);

// ...

我使用单元素数组/列表 - 相反,您可以“滥用”任何其他包装器对象,例如原子引用或线程本地作为包装器,因为您不能从内部直接将 a 分配给 Context 变量拉姆达。代码无法编译,因为从 lambda 内部引用的外部变量必须是有效的 final。

【讨论】:

  • 投反对票的原因是什么?我交付了 OP 要求的内容。我没有说这是个好主意。为什么不反馈我的答案中可以改进的地方?
  • 关于AtomicReference 的注释:Context 是根据Subscriber 定义的,但应该注意Mono 可以被视为Subscription 的惰性工厂,只要@ 987654331@ 决定订阅Mono。因此,将数据存储在像 AtomicReference 这样的外部状态是有问题的,因为多个订阅者会“共享”同一个 ref 并覆盖它。
  • @kriegaex 据我所知threadLocal 不应用于反应式。而且我无法从方面访问所需的变量。 Aspect 只有 pjp,我需要的变量是 http 请求标头。
  • @SimonBaslé 你知道如何以我需要的方式实现它吗?
  • 目前没有真正的方法可以做到这一点......只有 doOnEach 允许访问上下文,但它不会在订阅或取消时触发。你可以检测到完成和错误
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多