Browse Source

Adds exponential backoff config options to retry filter.

pull/1212/head
Anastasiia Smirnova 6 years ago committed by Spencer Gibb
parent
commit
7d78365648
No known key found for this signature in database
GPG Key ID: 7788A47380690861
  1. 24
      docs/src/main/asciidoc/spring-cloud-gateway.adoc
  2. 96
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java
  3. 22
      spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java

24
docs/src/main/asciidoc/spring-cloud-gateway.adoc

@ -1049,12 +1049,25 @@ spring: @@ -1049,12 +1049,25 @@ spring:
When a request is made through the gateway to `/name/bar/foo` the request made to `nameservice` will look like `http://nameservice/foo`.
=== Retry GatewayFilter Factory
The Retry GatewayFilter Factory takes `retries`, `statuses`, `methods`, and `series` as parameters.
The Retry GatewayFilter Factory support following set of parameters:
* `retries`: the number of retries that should be attempted
* `statuses`: the HTTP status codes that should be retried, represented using `org.springframework.http.HttpStatus`
* `methods`: the HTTP methods that should be retried, represented using `org.springframework.http.HttpMethod`
* `series`: the series of status codes to be retried, represented using `org.springframework.http.HttpStatus.Series`
* `exceptions`: list of exceptions thrown that should be retried
* `backoff`: configured exponential backoff for the retries. Retries are performed after a backoff interval of `firstBackoff * (factor ^ n)` where `n` is the iteration.
If `maxBackoff` is configured, the maximum backoff applied will be limited to `maxBackoff`.
If `basedOnPreviousValue` is true, backoff will be calculated using `prevBackoff * factor`.
The following defaults are configured for `Retry` filter if enabled:
* `retries` -- 3 times
* `series` -- 5XX series
* `methods` -- GET method
* `exceptions` -- `IOException` and `TimeoutException`
* `backoff` -- disabled
.application.yml
[source,yaml]
@ -1072,6 +1085,11 @@ spring: @@ -1072,6 +1085,11 @@ spring:
args:
retries: 3
statuses: BAD_GATEWAY
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
----
NOTE: The retry filter does not currently support retrying with a body (e.g. for POST or PUT requests with a body).
@ -1311,9 +1329,9 @@ To enable Gateway Metrics add spring-boot-starter-actuator as a project dependen @@ -1311,9 +1329,9 @@ To enable Gateway Metrics add spring-boot-starter-actuator as a project dependen
* `httpStatusCode`: Http Status of the request returned to the client
* `httpMethod`: The Http method used for the request
These metrics are then available to be scraped from ``/actuator/metrics/gateway.requests`` and can be easily integated with Prometheus to create a link:images/gateway-grafana-dashboard.jpeg[Grafana] link:gateway-grafana-dashboard.json[dashboard].
These metrics are then available to be scraped from ``/actuator/metrics/gateway.requests`` and can be easily integrated with Prometheus to create a link:images/gateway-grafana-dashboard.jpeg[Grafana] link:gateway-grafana-dashboard.json[dashboard].
NOTE: To enable the pometheus endpoint add micrometer-registry-prometheus as a project dependency.
NOTE: To enable the prometheus endpoint add micrometer-registry-prometheus as a project dependency.
=== Marking An Exchange As Routed

96
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.cloud.gateway.filter.factory;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -28,6 +29,7 @@ import org.apache.commons.logging.Log; @@ -28,6 +29,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import reactor.retry.Backoff;
import reactor.retry.Repeat;
import reactor.retry.RepeatContext;
import reactor.retry.Retry;
@ -105,6 +107,11 @@ public class RetryGatewayFilterFactory @@ -105,6 +107,11 @@ public class RetryGatewayFilterFactory
statusCodeRepeat = Repeat.onlyIf(repeatPredicate)
.doOnRepeat(context -> reset(context.applicationContext()));
BackoffConfig backoff = retryConfig.getBackoff();
if (backoff != null) {
statusCodeRepeat = statusCodeRepeat.backoff(getBackoff(backoff));
}
}
// TODO: support timeout, backoff, jitter, etc... in Builder
@ -132,6 +139,10 @@ public class RetryGatewayFilterFactory @@ -132,6 +139,10 @@ public class RetryGatewayFilterFactory
exceptionRetry = Retry.onlyIf(retryContextPredicate)
.doOnRetry(context -> reset(context.applicationContext()))
.retryMax(retryConfig.getRetries());
BackoffConfig backoff = retryConfig.getBackoff();
if (backoff != null) {
exceptionRetry = exceptionRetry.backoff(getBackoff(backoff));
}
}
GatewayFilter gatewayFilter = apply(retryConfig.getRouteId(), statusCodeRepeat,
@ -155,6 +166,11 @@ public class RetryGatewayFilterFactory @@ -155,6 +166,11 @@ public class RetryGatewayFilterFactory
};
}
private Backoff getBackoff(BackoffConfig backoff) {
return Backoff.exponential(backoff.firstBackoff, backoff.maxBackoff,
backoff.factor, backoff.basedOnPreviousValue);
}
public boolean exceedsMaxIterations(ServerWebExchange exchange,
RetryConfig retryConfig) {
Integer iteration = exchange.getAttribute(RETRY_ITERATION_KEY);
@ -240,6 +256,8 @@ public class RetryGatewayFilterFactory @@ -240,6 +256,8 @@ public class RetryGatewayFilterFactory
private List<Class<? extends Throwable>> exceptions = toList(IOException.class,
TimeoutException.class);
private BackoffConfig backoff;
public RetryConfig allMethods() {
return setMethods(HttpMethod.values());
}
@ -251,6 +269,25 @@ public class RetryGatewayFilterFactory @@ -251,6 +269,25 @@ public class RetryGatewayFilterFactory
|| !this.exceptions.isEmpty(),
"series, status and exceptions may not all be empty");
Assert.notEmpty(this.methods, "methods may not be empty");
if (this.backoff != null) {
this.backoff.validate();
}
}
public BackoffConfig getBackoff() {
return backoff;
}
public RetryConfig setBackoff(BackoffConfig backoff) {
this.backoff = backoff;
return this;
}
public RetryConfig setBackoff(Duration firstBackoff, Duration maxBackoff,
int factor, boolean basedOnPreviousValue) {
this.backoff = new BackoffConfig(firstBackoff, maxBackoff, factor,
basedOnPreviousValue);
return this;
}
@Override
@ -310,4 +347,63 @@ public class RetryGatewayFilterFactory @@ -310,4 +347,63 @@ public class RetryGatewayFilterFactory
}
public static class BackoffConfig {
private Duration firstBackoff = Duration.ofMillis(5);
private Duration maxBackoff;
private int factor = 2;
private boolean basedOnPreviousValue = true;
public BackoffConfig() {
}
public BackoffConfig(Duration firstBackoff, Duration maxBackoff, int factor,
boolean basedOnPreviousValue) {
this.firstBackoff = firstBackoff;
this.maxBackoff = maxBackoff;
this.factor = factor;
this.basedOnPreviousValue = basedOnPreviousValue;
}
public void validate() {
Assert.notNull(this.firstBackoff, "firstBackoff must be present");
}
public Duration getFirstBackoff() {
return firstBackoff;
}
public void setFirstBackoff(Duration firstBackoff) {
this.firstBackoff = firstBackoff;
}
public Duration getMaxBackoff() {
return maxBackoff;
}
public void setMaxBackoff(Duration maxBackoff) {
this.maxBackoff = maxBackoff;
}
public int getFactor() {
return factor;
}
public void setFactor(int factor) {
this.factor = factor;
}
public boolean isBasedOnPreviousValue() {
return basedOnPreviousValue;
}
public void setBasedOnPreviousValue(boolean basedOnPreviousValue) {
this.basedOnPreviousValue = basedOnPreviousValue;
}
}
}

22
spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java

@ -26,6 +26,7 @@ import com.netflix.loadbalancer.Server; @@ -26,6 +26,7 @@ import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -78,6 +79,18 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest @@ -78,6 +79,18 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
});
}
@Test
public void retryWithBackoff() {
// @formatter:off
testClient.get()
.uri("/retry?key=retry-with-backoff&count=3")
.header(HttpHeaders.HOST, "www.retrywithbackoff.org")
.exchange()
.expectStatus().isOk()
.expectHeader().value("X-Retry-Count", CoreMatchers.equalTo("3"));
// @formatter:on
}
@Test
public void retryFilterGetJavaDsl() {
testClient.get().uri("/retry?key=getjava&count=2")
@ -193,6 +206,15 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest @@ -193,6 +206,15 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest
.retry(config -> config.setRetries(2)
.setMethods(HttpMethod.POST, HttpMethod.GET)))
.uri(uri))
.route("retry_with_backoff", 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_loadbalancer",
r -> r.host("**.retrywithloadbalancer.org")
.filters(f -> f.prefixPath("/httpbin")

Loading…
Cancel
Save