From 350c9cda6dcce96f76f41b91e64bf8884c6d440e Mon Sep 17 00:00:00 2001 From: jiangyuan Date: Thu, 6 Mar 2025 21:26:50 +0800 Subject: [PATCH 1/5] RetryGatewayFilterFactory RetryConfig support Jitter & Timeout Signed-off-by: jiangyuan --- .../factory/RetryGatewayFilterFactory.java | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java index d8046e27..ad6889e5 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java @@ -30,6 +30,7 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import reactor.netty.Connection; import reactor.retry.Backoff; +import reactor.retry.Jitter; import reactor.retry.Repeat; import reactor.retry.RepeatContext; import reactor.retry.Retry; @@ -70,7 +71,7 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory shortcutFieldOrder() { return Arrays.asList("retries", "statuses", "methods", "backoff.firstBackoff", "backoff.maxBackoff", - "backoff.factor", "backoff.basedOnPreviousValue"); + "backoff.factor", "backoff.basedOnPreviousValue", "jitter.randomFactor", "timeout"); } @Override @@ -124,9 +125,16 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory exceptionRetry = null; if (!retryConfig.getExceptions().isEmpty()) { @@ -163,6 +171,14 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory= 0"); + } + } + + public Duration getTimeout() { + return timeout; + } + + public RetryConfig setTimeout(Duration timeout) { + this.timeout = timeout; + return this; + } + + public JitterConfig getJitter() { + return jitter; + } + + public RetryConfig setJitter(JitterConfig jitter) { + this.jitter = jitter; + return this; } public BackoffConfig getBackoff() { @@ -439,4 +487,28 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory= 0 && randomFactor <= 1, "random factor must be between 0 and 1 (default 0.5)"); + } + + public JitterConfig() { + } + + public JitterConfig(double randomFactor) { + this.randomFactor = randomFactor; + } + + public double getRandomFactor() { + return randomFactor; + } + + public void setRandomFactor(double randomFactor) { + this.randomFactor = randomFactor; + } + } + } From 9eb9165927d1d12af1a61b3d3675502448b68572 Mon Sep 17 00:00:00 2001 From: jiangyuan Date: Wed, 12 Mar 2025 15:44:15 +0800 Subject: [PATCH 2/5] add docs and unit-test Signed-off-by: jiangyuan --- .../retry-factory.adoc | 13 ++++++++++++- .../factory/RetryGatewayFilterFactory.java | 4 +++- ...yGatewayFilterFactoryIntegrationTests.java | 12 ++++++++++++ .../test/resources/application-retrytests.yml | 19 +++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway/gatewayfilter-factories/retry-factory.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway/gatewayfilter-factories/retry-factory.adoc index 62d58904..afc650f5 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway/gatewayfilter-factories/retry-factory.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway/gatewayfilter-factories/retry-factory.adoc @@ -12,6 +12,9 @@ The `Retry` `GatewayFilter` factory supports the following parameters: Retries are performed after a backoff interval of `firstBackoff * (factor ^ n)`, where `n` is the iteration. If `maxBackoff` is configured, the maximum backoff applied is limited to `maxBackoff`. If `basedOnPreviousValue` is true, the backoff is calculated by using `prevBackoff * factor`. +* `jitter`: The configured random jitter for the retries. +Generating a backoff between `[backoff - backoff*randomFactor, backoff + backoff*randomFactor]` +* `timeout`: The configured timeout for the retries. The following defaults are configured for `Retry` filter, if enabled: @@ -20,6 +23,8 @@ The following defaults are configured for `Retry` filter, if enabled: * `methods`: GET method * `exceptions`: `IOException` and `TimeoutException` * `backoff`: disabled +* `jitter`: disabled +* `timeout`: unlimited The following listing configures a Retry `GatewayFilter`: @@ -45,6 +50,9 @@ spring: maxBackoff: 50ms factor: 2 basedOnPreviousValue: false + jitter: + randomFactor: 0.5 + timeout: 100ms ---- NOTE: When using the retry filter with a `forward:` prefixed URL, the target endpoint should be written carefully so that, in case of an error, it does not do anything that could result in a response being sent to the client and committed. @@ -77,10 +85,13 @@ spring: maxBackoff: 50ms factor: 2 basedOnPreviousValue: false + jitter: + randomFactor: 0.5 + timeout: 100ms - id: retryshortcut_route uri: https://example.org filters: - - Retry=3,INTERNAL_SERVER_ERROR,GET,10ms,50ms,2,false + - Retry=3,INTERNAL_SERVER_ERROR,GET,10ms,50ms,2,false,0.5,100ms ---- diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java index ad6889e5..91a73ae0 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java @@ -492,7 +492,8 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory= 0 && randomFactor <= 1, "random factor must be between 0 and 1 (default 0.5)"); + Assert.isTrue(randomFactor >= 0 && randomFactor <= 1, + "random factor must be between 0 and 1 (default 0.5)"); } public JitterConfig() { @@ -509,6 +510,7 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory Date: Thu, 13 Mar 2025 12:31:46 +0800 Subject: [PATCH 3/5] add unit-test Signed-off-by: jiangyuan --- .../factory/RetryGatewayFilterFactory.java | 9 ++++++-- ...yGatewayFilterFactoryIntegrationTests.java | 21 +++++++++++++++++++ .../test/resources/application-retrytests.yml | 19 ----------------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java index 91a73ae0..1f42e412 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java @@ -173,11 +173,11 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory timeout + testClient.get() + .uri("/retry?key=retry-with-backoff-timeout&count=3") + .header(HttpHeaders.HOST, "www.retrywithbackofftimeout.org") + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + } + @Test public void retryFilterGetJavaDsl() { testClient.get() @@ -375,6 +386,16 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest r -> r.host("**.retrywithbackoff.org").filters(f -> f.prefixPath("/httpbin").retry(config -> { config.setRetries(2).setBackoff(Duration.ofMillis(100), null, 2, true); })).uri(uri)) + .route("retry_with_backoff_jitter_timeout_test", + r -> r.host("**.retrywithbackoffjittertimeout.org").filters(f -> f.prefixPath("/httpbin").retry(config -> { + config.setRetries(3).setBackoff(Duration.ofMillis(50), Duration.ofMillis(100), 2, true) + .setJitter(0.1).setTimeout(Duration.ofMillis(1000)); + })).uri(uri)) + .route("retry_with_backoff_timeout_test", + r -> r.host("**.retrywithbackofftimeout.org").filters(f -> f.prefixPath("/httpbin").retry(config -> { + config.setRetries(3).setBackoff(Duration.ofMillis(100), null, 2, true) + .setTimeout(Duration.ofMillis(100)); + })).uri(uri)) .route("retry_with_loadbalancer", r -> r.host("**.retrywithloadbalancer.org") diff --git a/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml b/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml index c9f8cc8c..ea5e1be8 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml @@ -29,22 +29,3 @@ spring: - name: Retry args: methods: GET,POST - # ===================================== - - id: retry_with_backoff_jitter_timeout_test - uri: ${test.uri} - predicates: - - Host=**.retrywithbackoffjittertimeout.org - filters: - - name: Retry - args: - retries: 3 - statuses: INTERNAL_SERVER_ERROR - methods: GET - backoff: - firstBackoff: 10ms - maxBackoff: 50ms - factor: 2 - basedOnPreviousValue: false - jitter: - randomFactor: 0.5 - timeout: 200ms \ No newline at end of file From 5e9753efb6a5400ec69ffa85e899799331ef3a06 Mon Sep 17 00:00:00 2001 From: joecqupt Date: Sat, 15 Mar 2025 13:47:18 +0800 Subject: [PATCH 4/5] add toString info Signed-off-by: joecqupt --- .../factory/RetryGatewayFilterFactory.java | 20 ++++++++++- ...yGatewayFilterFactoryIntegrationTests.java | 35 +++++++++++-------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java index 1f42e412..e47307da 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java @@ -42,6 +42,7 @@ import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.support.HasRouteId; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.cloud.gateway.support.TimeoutException; +import org.springframework.core.style.ToStringCreator; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus.Series; @@ -135,7 +136,6 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory exceptionRetry = null; if (!retryConfig.getExceptions().isEmpty()) { Predicate> retryContextPredicate = context -> { @@ -196,6 +196,9 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory timeout testClient.get() - .uri("/retry?key=retry-with-backoff-timeout&count=3") - .header(HttpHeaders.HOST, "www.retrywithbackofftimeout.org") - .exchange() - .expectStatus() - .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + .uri("/retry?key=retry-with-backoff-timeout&count=3") + .header(HttpHeaders.HOST, "www.retrywithbackofftimeout.org") + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); } @Test @@ -386,16 +386,21 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest r -> r.host("**.retrywithbackoff.org").filters(f -> f.prefixPath("/httpbin").retry(config -> { config.setRetries(2).setBackoff(Duration.ofMillis(100), null, 2, true); })).uri(uri)) - .route("retry_with_backoff_jitter_timeout_test", - r -> r.host("**.retrywithbackoffjittertimeout.org").filters(f -> f.prefixPath("/httpbin").retry(config -> { - config.setRetries(3).setBackoff(Duration.ofMillis(50), Duration.ofMillis(100), 2, true) - .setJitter(0.1).setTimeout(Duration.ofMillis(1000)); - })).uri(uri)) - .route("retry_with_backoff_timeout_test", - r -> r.host("**.retrywithbackofftimeout.org").filters(f -> f.prefixPath("/httpbin").retry(config -> { - config.setRetries(3).setBackoff(Duration.ofMillis(100), null, 2, true) - .setTimeout(Duration.ofMillis(100)); - })).uri(uri)) + .route("retry_with_backoff_jitter_timeout_test", r -> r.host("**.retrywithbackoffjittertimeout.org") + .filters(f -> f.prefixPath("/httpbin").retry(config -> { + config.setRetries(3) + .setBackoff(Duration.ofMillis(50), Duration.ofMillis(100), 2, true) + .setJitter(0.1) + .setTimeout(Duration.ofMillis(1000)); + })) + .uri(uri)) + .route("retry_with_backoff_timeout_test", r -> r.host("**.retrywithbackofftimeout.org") + .filters(f -> f.prefixPath("/httpbin").retry(config -> { + config.setRetries(3) + .setBackoff(Duration.ofMillis(100), null, 2, true) + .setTimeout(Duration.ofMillis(100)); + })) + .uri(uri)) .route("retry_with_loadbalancer", r -> r.host("**.retrywithloadbalancer.org") From 0f3112077d0ac34eb594f735b42c1ca27a38bd30 Mon Sep 17 00:00:00 2001 From: jiangyuan Date: Tue, 18 Mar 2025 15:46:14 +0800 Subject: [PATCH 5/5] update unit test Signed-off-by: jiangyuan --- .../factory/RetryGatewayFilterFactoryIntegrationTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java index bacd256c..fb1439d1 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java @@ -130,6 +130,7 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest .exchange() .expectStatus() .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + assertThat(TestConfig.map.get("retry-with-backoff-timeout")).isNotNull().hasValue(2); } @Test @@ -398,7 +399,7 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest .filters(f -> f.prefixPath("/httpbin").retry(config -> { config.setRetries(3) .setBackoff(Duration.ofMillis(100), null, 2, true) - .setTimeout(Duration.ofMillis(100)); + .setTimeout(Duration.ofMillis(200)); })) .uri(uri))