Merge branch '2.1.x'
This commit is contained in:
@@ -1101,12 +1101,25 @@ spring:
|
||||
When a request is made through the gateway to `/name/bar/foo` the request made to `nameservice` will look like `https://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]
|
||||
@@ -1124,6 +1137,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).
|
||||
@@ -1363,9 +1381,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
|
||||
|
||||
|
||||
@@ -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;
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
|| !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
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@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
|
||||
.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")
|
||||
|
||||
Reference in New Issue
Block a user