HttpClient asyncRequest 和指数退避

HttpClient asyncRequest and exponential backoff

我需要对可能失败的请求实施指数退避。但是,它是作为异步请求实现的。如果这是同步完成的,我会更好地了解将延迟放在哪里。粗略地说,我认为它会像这样工作:

// These would be configurable in application.yml
currentAttempt = 0;
maxAttempts = 3;
timeoutGrowth = 2;
currentDelayTime = 5ms;
repeatNeeded = false;
while(repeatNeeded && currentAttempt < maxAttempts) {
    httpStatusCode = makeRequest(someService)
    if(httpStatusCode == 503) {
        repeatNeeded=true;
        currentAttempt++;
        currentDelayTime*=timeoutGrowthRate;
        sleep(currentDelayTime)
    }
}

但是,通过异步调用,函数的调用者有时间做其他事情,直到 Future 有东西。我是在下面的 getObservations() 方法中编写回退代码,还是在该 getObservations() 方法的调用方中编写代码?以下是当前的调用:

public CompletableFuture getObservations(String text, Map bodyParams) 抛出 URISyntaxException { URI uri = getUri(文本); HttpRequest 请求 = getRequest(uri, text, bodyParams); Map contextMap = Optional.ofNullable(MDC.getCopyOfContextMap()).orElse(Collections.emptyMap());

    Instant startTime = Instant.now();

    return httpClient.sendAsync(request, BodyHandlers.ofString())
            .exceptionally(ex -> {
                throw new ExternalToolException(externalServiceConfig.getName(), ex);
            })
            .thenApply(response -> {
                long toolRequestDurationMillis = ChronoUnit.MILLIS.between(startTime, Instant.now());
                if (HttpStatus.valueOf(response.statusCode()).is2xxSuccessful()) {
                    ToolResponse toolResponse = processResponse(response, toolRequestDurationMillis);
                    logToolResponse(toolResponse);
                    return toolResponse;
                }

                log.error("{} returned non-200 response code: {}", externalServiceConfig.getName(), response.statusCode());
                throw new ExternalToolException(externalServiceConfig.getName(), response.statusCode());
            });

}


如果您可以考虑使用具有非常强大 API 包括重试的响应式 java。例如,

request()
  .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)));

有更多选项,例如仅针对特定异常重试或定义最大退避

request()
  .retryWhen(Retry.backoff(3, Duration.ofSeconds(2)));
  .maxBackoff(5)
  .filter(throwable -> isRetryableError(throwable))

您可以使用 WebClient,它是一个 non-blocking 客户端,通过底层 HTTP 客户端库(例如 Reactor Netty

公开流畅的反应式 API
webClient.get()
        .uri("/api")
        .retrieve()
        .bodyToMono(String.class)
        .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)));

如果出于某种原因,你仍然想使用 HttpClient,你可以换行 CompletableFuture

Mono.fromFuture(httpClient.sendAsync(request, BodyHandlers.ofString()))
  .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)));