我正在使用transformDeferred方法调用一个服务类方法:
public Mono<SPathResponse> getPath(SPathRequest request) {
return pathService.getPath(request)
.transformDeferred(RetryOperator.of(retry));
}
我的重试配置:
Retry retry = Retry.of("es", RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.of(30, ChronoUnit.MILLIS))
.writableStackTraceEnabled(true)
.failAfterMaxAttempts(true)
.retryOnException(throwable -> throwable instanceof RuntimeException)
.build());
测试方法:
@Test
void shouldRetry() {
BDDMockito.given(pathService.getPath(any(SPathRequest.class)))
.willReturn(Mono.error(RuntimeException::new))
.willReturn(Mono.just(SPathResponse.builder().build()));
cachedPathService.getPath(SPathRequest.builder()
.sourceNodeId("2")
.sourceCategoryId("1234")
.destinationNodeId("123")
.build())
.as(StepVerifier::create)
.expectError(RuntimeException.class)
.verify();
var captor = ArgumentCaptor.forClass(SPathRequest.class);
BDDMockito.then(pathService).should(times(2)).getPath(captor.capture());
运行它时,我确实得到了预期的异常,但"getPath"只被调用一次。
我可能错过了一些东西,因为重试机制应该在第二次调用时重试并返回存根结果,这应该会使测试失败,因为没有发生异常,实际结果应该是预期的。
我的配置有什么问题?
编辑:我想要这个片段的等价物(来自reslience4j reactor示例(,用于直接在我的Mono上调用,而不是用Mono包装函数。fromCallable(:
@Test
public void returnOnErrorUsingMono() {
RetryConfig config = retryConfig();
Retry retry = Retry.of("testName", config);
RetryOperator<String> retryOperator = RetryOperator.of(retry);
given(helloWorldService.returnHelloWorld())
.willThrow(new HelloWorldException());
StepVerifier.create(Mono.fromCallable(helloWorldService::returnHelloWorld)
.transformDeferred(retryOperator))
.expectSubscription()
.expectError(HelloWorldException.class)
.verify(Duration.ofSeconds(1));
StepVerifier.create(Mono.fromCallable(helloWorldService::returnHelloWorld)
.transformDeferred(retryOperator))
.expectSubscription()
.expectError(HelloWorldException.class)
.verify(Duration.ofSeconds(1));
then(helloWorldService).should(times(6)).returnHelloWorld();
Retry.Metrics metrics = retry.getMetrics();
assertThat(metrics.getNumberOfFailedCallsWithRetryAttempt()).isEqualTo(2);
assertThat(metrics.getNumberOfFailedCallsWithoutRetryAttempt()).isEqualTo(0);
}
retryConfig的定义如下:
private RetryConfig retryConfig() {
return RetryConfig.custom()
.waitDuration(Duration.ofMillis(10))
.build();
}
谢谢。
你很久以前就问过这个问题了,但我找不到围绕这个主题的任何其他相关答案,所以我想我会在这里发布我的发现,以防对某人有利。我的代码在Kotlin中,但如果您愿意,它应该能够很容易地将其转换为Java。
我真的找不到你的代码有什么问题,就我所见,一切似乎都很正常,所以问题可能在于你的问题中没有显示的东西。我只会发布一个对我很有效的设置。
因此,首先,配置您的Resilience4j设置:
import io.github.resilience4j.core.IntervalFunction
import io.github.resilience4j.retry.RetryConfig
import io.github.resilience4j.retry.RetryRegistry
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.client.WebClientRequestException
import java.io.IOException
@Configuration
class Resilience4jConfiguration {
companion object {
const val YOUR_SERVICE = "yourService"
}
@Bean
fun retryRegistry(): RetryRegistry {
val config: RetryConfig = RetryConfig.custom<YourResponse>()
.maxAttempts(5)
.retryExceptions(IOException::class.java, WebClientRequestException::class.java)
.intervalFunction(IntervalFunction.ofExponentialBackoff(100, 2.0))
.build()
return RetryRegistry.of(config)
}
@Bean
fun retryLogger(retryRegistry: RetryRegistry) = RetryLogger(retryRegistry)
}
class RetryLogger(
retryRegistry: RetryRegistry
) {
companion object {
private val logger = logger()
}
init {
retryRegistry.retry(YOUR_SERVICE)
.eventPublisher
.onRetry {
logger.info("Retrying: $it")
}
}
}
现在您可以在客户端代码中使用它,例如:
import io.github.resilience4j.reactor.retry.RetryOperator
import io.github.resilience4j.retry.RetryRegistry
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
@Component
class YourClient(
retryRegistry: RetryRegistry,
@Value("${your.url}") private val baseUrl: String
) {
private val webClient = WebClient.builder().baseUrl(baseUrl).build()
private val retry = retryRegistry.retry(YOUR_SERVICE)
fun performRequest(id: String): Mono<YourResponse> =
webClient
.get()
.uri("/$id")
.retrieve()
.bodyToMono(YourResponse::class.java)
.transformDeferred(RetryOperator.of(retry))
.onErrorResume { fallback(it) }
fun fallback(e: Throwable): Mono<YourResponse> = Mono.error(RuntimeException("Tried too many times"))
}
通过这种设置,我确实看到了正在执行的重试。希望这能有所帮助。
EDIT:上面的代码使用Spring Boot 3.1.1和Resilience 4j 2.1.0工作,您需要这两个依赖项:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-reactor</artifactId>
<version>${resilience4j.version}</version>
</dependency>