如何测量 webflux WebClient 方法的执行时间?

时间:2021-04-12 16:31:09

标签: java spring spring-webflux project-reactor

我准备了一堆要并行发送到外部网络服务的请求。 在这个流程中,我继续直接处理响应(例如向数据库中插入一些东西)。

问题:我想跟踪最大请求时间(对于一个请求!),不包括处理。 但正如所写,这只会跟踪包括任何子进程在内的全局时间:

StopWatch watch = new StopWatch();
watch.start();

Flux.fromIterable(requests)
    .flatMap(req -> webClient.send(req, MyResponse.class)
            .doOnSuccess(rsp -> processResponse(rsp))) //assume some longer routine
    .collectList()
    .block();
    
watch.stop();
System.out.println(w.getTotalTimeMillis());
            

问题:如何衡量请求花费的最长时间,不包括 processResponse() 时间?

4 个答案:

答案 0 :(得分:1)

在单声道上使用 elapsed 时,您将返回一个包含已用时间和原始对象的元组的单声道。您必须打开它们才能使用它们。我在测试中编写了一个示例(从您的代码中简化了一些)以查看它的工作情况:

@Test
public void elapsed() {

    Flux.fromIterable(List.of(1, 2, 3, 4, 5))
        .flatMap(req -> Mono.delay(Duration.ofMillis(100L * req))
                            .map(it -> "response_" + req)
                            .elapsed()
                            .doOnNext(it -> System.out.println("I took " + it.getT1() + " MS"))
                            .map(Tuple2::getT2)
                            .doOnSuccess(rsp -> processResponse(rsp)))
        .collectList()
        .block();

}

@SneakyThrows
public void processResponse(Object it) {
    System.out.println("This is the response: " + it);
    Thread.sleep(1000);
}

输出如下:

I took 112 MS
This is the response: response_1
I took 205 MS
This is the response: response_2
I took 305 MS
This is the response: response_3
I took 403 MS
This is the response: response_4
I took 504 MS
This is the response: response_5

这些数字既代表了延迟(在您的情况下是 webClient.send()),也代表了反应式管道本身的一些开销。它是在订阅(在特定请求的 flatMap 运行时发生)和下一个信号(在我的情况下来自地图的结果,在您的情况下是网络客户端请求的结果)之间计算的

你的代码看起来像这样:

Flux.fromIterable(requests)
        .flatMap(req -> webClient.send(req, MyResponse.class)
                                 .elapsed()
                                 .doOnNext(it -> System.out.println("I took " + it.getT1() + " MS"))
                                 .map(Tuple2::getT2)
                                 .doOnSuccess(rsp -> processResponse(rsp))) //assume some longer routine
        .collectList()
        .block();

请注意,如果您想改用秒表,也可以执行以下操作:

Flux.fromIterable(List.of(1, 2, 3, 4, 5)).flatMap(req -> {
            StopWatch stopWatch = new StopWatch();
            return Mono.fromRunnable(stopWatch::start)
                       .then(Mono.delay(Duration.ofMillis(100L * req)).map(it -> "response_" + req).doOnNext(it -> {
                           stopWatch.stop();
                           System.out.println("I took " + stopWatch.getTime() + " MS");
                       }).doOnSuccess(this::processResponse));
        }).collectList().block();

但我个人会推荐 .elapsed() 解决方案,因为它更简洁。

答案 1 :(得分:0)

我会避免在那种方法中直接使用秒表。而是创建一个也可以在其他地方使用的指标包装器。

您可以利用 .doOnSubscribe(), .doOnError(), .doOnSuccess() 但是要回答你的问题,你可以有一个像这样的计时器


    public sendRequest(){
                Flux.fromIterable(requests)
                .flatMap(req -> webClient.send(req, MyResponse.class)
                        .transform(timerPublisher("time took for ", req.id)))
                .collectList()
                .block();
   }

//this can be made sophisticated by determining what kind of publisher it is
//mono or flux
    private Function<Mono<T>, Publisher<T>> timerPublisher(String metric) {

        StopWatchHelper stopWatch = new StopWatchHelper(metric);
        return s -> s.doOnSubscribe((s) -> stopWatch.start())
                .doOnSuccess(documentRequest -> stopWatch.record())
                .doOnError(stopWatch::record);
    }

    private class StopWatchHelper{
        private StopWatch stopWatch;
        private String metric;
        public StopWatchHelper(String metric){
            this.metric = metric;
            stopWatch = new StopWatch();
        }
        public Consumer<Subscription> start() {
            return (s) -> stopWatch.start();
        }

        public void record(){
            if(stopWatch.isStarted()){
                System.out.println(String.format("Metric %s took %s", metric, stopWatch.getTime()));
            }
        }

        public void record(Throwable t){
            if(stopWatch.isStarted()){
                System.out.println(String.format("Metric %s took %s, reported in error %s", metric, stopWatch.getTime(),throwable));
            }
        }
    }


PS: Avoid using .block() -> it beats the purpose :) 

答案 2 :(得分:-1)

Spring Boot 提供了一个开箱即用的功能,可以为您的 WebClient 添加检测。

您可以通过使用自动配置的 WebClient.Builder 创建您的 WebClient 实例来“启用”这些指标,即。

@Bean
public WebClient myCustomWebClient(WebClient.Builder builder) {
    return builder
            // your custom web client config code
            .build();
}

此检测将对您的 WebClient 进行的每个 API 调用计时,并将其注册到您配置的 MeterRegistry

Reference Docs

答案 3 :(得分:-1)

一种选择是使用单元测试和 pow 来模拟方法 Mockito 的行为。然后你只测量其他任务的时间。假设你在一个类中有这个方法:

processResponse()

然后您使用 Mockito 中的 public class AnotherService { public Object processResponse(Object response) { try { System.out.println("processResponse called"); Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); } return response; } } 方法并模拟返回。在这里,您已经摆脱了 when 的时间 Thread.sleep(20000);

processResponse

要在您的单元测试中使用它,它将如下所示:

Object sample = new Object(); // your return to sumulate
when(anotherService.processResponse(any())).thenReturn(sample);

因此,当您测试 import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class GitHubJobsClientMockTest { @Mock private AnotherService anotherService; @InjectMocks private YourService yourService; void stackoverflowRequest() { Object sample = new Object(); // HERE YOU CREATE THE MOCK OF YOUR METHOD FROM AnotherService when(anotherService.processResponse(any())).thenReturn(sample); List<Integer> pageNumbers = List.of(1, 2, 3); String description = "Java"; List<Stream<Object>> result = yourService.stackoverflowRequest(pageNumbers, description); assertTrue(result.size() > 0); } } 时,YourServicestopWatch.start(); 应计算总时间 - 模拟方法 stopWatch.stop(); 的时间。

anotherService.processResponse(response))
相关问题