如何计算 Spring-Retry 中断路器的正确超时?
How to calculate right timeouts for CircuitBreaker in Spring-Retry?
我了解 @CircuitBreaker
注释应该如何工作,但不确定 resetTimeout
参数值。
我有一个应用程序每隔 10 秒就会不断询问其他外部服务。每个请求可能需要 1-3 秒。默认三次尝试就可以了,所以我猜 openTimeout
中的 40 秒应该没问题。我决定将 80 秒设置为 resetTimeout
。据我了解文档,如果 3 次故障将在 40 秒内发生,则电路将在 resetTimeout 参数(80 秒)中指定的时间段内打开。
在测试过程中,我注意到如果电路打开并且有另一个请求进入,则不会调用外部服务。没关系,但我希望在 resetTimeout
(80 秒)之后将调用从未发生过的真实服务。
奇怪的是,如果电路打开并且在 resetTimeout(80s) 的时间内没有调用服务,那么电路将重置。恕我直言 spring 应该比较当前时间和开路失败,目前看起来它是将当前时间与上次请求进行比较。
我是不是误解了这个库的想法?
服务:
package test;
import org.springframework.retry.annotation.CircuitBreaker;
import org.springframework.retry.annotation.Recover;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
@Component
class ConnectionService {
@CircuitBreaker(include = { RestClientException.class }, openTimeout = 10_000, resetTimeout = 20_000)
String doUpload(String payload) {
if(payload.contentEquals("FAIL")) {
throw new RestClientException("");
}
System.out.println("real service called");
//callling external service....
return payload;
}
@Recover
public String recover(String payload) {
return "recovered";
}
}
测试:
package test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.annotation.EnableRetry;
@RunWith(MockitoJUnitRunner.class)
public class SpringRetryTest {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
SpringRetryTest.TestConfiguration.class);
ConnectionService connectionService = context.getBean(ConnectionService.class);
@Test
public void testCircuitBreaker() throws InterruptedException {
incorrectStep();
incorrectStep();
incorrectStep();
incorrectStep();
incorrectStep();
System.out.println();
System.out.println();
System.out.println();
final long l = System.currentTimeMillis();
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
//wait more than resetTimeout
System.out.println();
System.out.println();
System.out.println();
Thread.sleep(21_000L);
correctStep(l);
}
private void incorrectStep() throws InterruptedException {
doFailedUpload(connectionService);
Thread.sleep(1_000L);
System.out.println();
}
private void correctStep(final long l) throws InterruptedException {
doCorrectUpload(connectionService);
Thread.sleep(1_000L);
printTime(l);
}
private void printTime(final long l) {
System.out.println(String.format("%d ms after last failure", (System.currentTimeMillis() - l)));
}
private void doFailedUpload(ConnectionService externalService) throws InterruptedException {
System.out.println("before fail");
externalService.doUpload("FAIL");
System.out.println("after fail");
Thread.sleep(900);
}
private void doCorrectUpload(ConnectionService externalService) throws InterruptedException {
System.out.println("before ok");
externalService.doUpload("");
System.out.println("after ok");
Thread.sleep(900);
}
@Configuration
@EnableRetry
protected static class TestConfiguration {
@Bean
public ConnectionService externalService() {
return new ConnectionService();
}
@Bean
public RetryListener retryListener1() {
return new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(final RetryContext retryContext, final RetryCallback<T, E> retryCallback) {
System.out.println("----/ ---- open, retry count:" + retryContext.getRetryCount());
return true;
}
@Override
public <T, E extends Throwable> void close(final RetryContext retryContext, final RetryCallback<T, E> retryCallback, final Throwable throwable) {
System.out.println("---------- close, retry count:" + retryContext.getRetryCount());
}
@Override
public <T, E extends Throwable> void onError(final RetryContext retryContext, final RetryCallback<T, E> retryCallback, final Throwable throwable) {
System.out.println("onError, retry count:" + retryContext.getRetryCount());
}
};
}
}
}
我正在使用 osx 10.12.6、java 1.8.0_144 spring:4.2.3.RELEASE 和 spring- retry:1.2.1.RELEASE(我的同事也测试了 1.2.2,结果相同)
发布已解决:https://github.com/spring-projects/spring-retry/issues/99
尚未发布,目前可作为快照使用。
我了解 @CircuitBreaker
注释应该如何工作,但不确定 resetTimeout
参数值。
我有一个应用程序每隔 10 秒就会不断询问其他外部服务。每个请求可能需要 1-3 秒。默认三次尝试就可以了,所以我猜 openTimeout
中的 40 秒应该没问题。我决定将 80 秒设置为 resetTimeout
。据我了解文档,如果 3 次故障将在 40 秒内发生,则电路将在 resetTimeout 参数(80 秒)中指定的时间段内打开。
在测试过程中,我注意到如果电路打开并且有另一个请求进入,则不会调用外部服务。没关系,但我希望在 resetTimeout
(80 秒)之后将调用从未发生过的真实服务。
奇怪的是,如果电路打开并且在 resetTimeout(80s) 的时间内没有调用服务,那么电路将重置。恕我直言 spring 应该比较当前时间和开路失败,目前看起来它是将当前时间与上次请求进行比较。
我是不是误解了这个库的想法?
服务:
package test;
import org.springframework.retry.annotation.CircuitBreaker;
import org.springframework.retry.annotation.Recover;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
@Component
class ConnectionService {
@CircuitBreaker(include = { RestClientException.class }, openTimeout = 10_000, resetTimeout = 20_000)
String doUpload(String payload) {
if(payload.contentEquals("FAIL")) {
throw new RestClientException("");
}
System.out.println("real service called");
//callling external service....
return payload;
}
@Recover
public String recover(String payload) {
return "recovered";
}
}
测试:
package test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.annotation.EnableRetry;
@RunWith(MockitoJUnitRunner.class)
public class SpringRetryTest {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
SpringRetryTest.TestConfiguration.class);
ConnectionService connectionService = context.getBean(ConnectionService.class);
@Test
public void testCircuitBreaker() throws InterruptedException {
incorrectStep();
incorrectStep();
incorrectStep();
incorrectStep();
incorrectStep();
System.out.println();
System.out.println();
System.out.println();
final long l = System.currentTimeMillis();
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
correctStep(l);
//wait more than resetTimeout
System.out.println();
System.out.println();
System.out.println();
Thread.sleep(21_000L);
correctStep(l);
}
private void incorrectStep() throws InterruptedException {
doFailedUpload(connectionService);
Thread.sleep(1_000L);
System.out.println();
}
private void correctStep(final long l) throws InterruptedException {
doCorrectUpload(connectionService);
Thread.sleep(1_000L);
printTime(l);
}
private void printTime(final long l) {
System.out.println(String.format("%d ms after last failure", (System.currentTimeMillis() - l)));
}
private void doFailedUpload(ConnectionService externalService) throws InterruptedException {
System.out.println("before fail");
externalService.doUpload("FAIL");
System.out.println("after fail");
Thread.sleep(900);
}
private void doCorrectUpload(ConnectionService externalService) throws InterruptedException {
System.out.println("before ok");
externalService.doUpload("");
System.out.println("after ok");
Thread.sleep(900);
}
@Configuration
@EnableRetry
protected static class TestConfiguration {
@Bean
public ConnectionService externalService() {
return new ConnectionService();
}
@Bean
public RetryListener retryListener1() {
return new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(final RetryContext retryContext, final RetryCallback<T, E> retryCallback) {
System.out.println("----/ ---- open, retry count:" + retryContext.getRetryCount());
return true;
}
@Override
public <T, E extends Throwable> void close(final RetryContext retryContext, final RetryCallback<T, E> retryCallback, final Throwable throwable) {
System.out.println("---------- close, retry count:" + retryContext.getRetryCount());
}
@Override
public <T, E extends Throwable> void onError(final RetryContext retryContext, final RetryCallback<T, E> retryCallback, final Throwable throwable) {
System.out.println("onError, retry count:" + retryContext.getRetryCount());
}
};
}
}
}
我正在使用 osx 10.12.6、java 1.8.0_144 spring:4.2.3.RELEASE 和 spring- retry:1.2.1.RELEASE(我的同事也测试了 1.2.2,结果相同)
发布已解决:https://github.com/spring-projects/spring-retry/issues/99
尚未发布,目前可作为快照使用。