[英]How to measure execution time of webflux WebClient methods?
我准备了一堆要并行发送到外部 Web 服务的请求。 在这个流程中,我继续直接处理响应(例如向数据库中插入一些东西)。
问题:我想跟踪最大请求时间(对于一个请求)。 不包括处理,但正如所写:这将只跟踪包括任何子进程的全局时间:
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()
时间?
在 mono 上使用 elapsed 时,您将返回一个包含已用时间和原始 object 的元组的 mono。 您必须打开它们才能使用它们。 我在测试中编写了一个示例(从您的代码中简化了一点)以查看它的工作原理:
@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);
}
output 看起来像这样:
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 运行时发生)和下一个信号(在我的情况下是来自 map 的结果,在你的情况下是 webclient 请求的结果)之间计算的
你的代码看起来像这样:
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();
但我个人会推荐 the.elapsed() 解决方案,因为它更干净一些。
我会直接用那种方法避免秒表。 而是创建一个也可以在其他地方使用的指标包装器。
您可以利用.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 :)
Spring 启动提供了一个开箱即用的功能,可以将检测添加到您的WebClient
。
您可以通过使用自动配置的WebClient.Builder
来“启用”这些指标来创建您的WebClient
实例,即。
@Bean
public WebClient myCustomWebClient(WebClient.Builder builder) {
return builder
// your custom web client config code
.build();
}
此工具将为您的WebClient
发出的每个单独的 API 调用计时,并将其注册到您配置的MeterRegistry
一种选择是使用单元测试和Mockito
来模拟方法processResponse()
的行为。 然后你只测量其他任务的时间。 假设您在 class 中有此方法:
public class AnotherService {
public Object processResponse(Object response) {
try {
System.out.println("processResponse called");
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return response;
}
}
然后使用 Mockito 中的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);
}
}
因此,当您测试 YourService 时, YourService
stopWatch.start();
和stopWatch.stop();
应该计算总时间 - 模拟方法anotherService.processResponse(response))
的时间。
public class YourService {
public List<Stream<Object>> stackoverflowRequest(List<Integer> requests, String description) {
stopWatch.start();
List<Stream<GitHubPosition>> result = Flux.fromIterable(requests)
.map(pageNumber -> invokeGithubJobsApi(pageNumber, description))
.map(gitHubPositionList ->
gitHubPositionList
.stream()
// THIS METHOD YOU HAVE TO MOCK
.map(response -> anotherService.processResponse(response))
)
.collectList()
.block();
stopWatch.stop();
log.info("time elapsed: " + stopWatch.getTime());
return result;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.