Order
This article mainly studies spring cloud gateway’s RetryGatewayFilter.
GatewayAutoConfiguration
spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java
@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
@AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
//......
@Bean
public RetryGatewayFilterFactory retryGatewayFilterFactory() {
return new RetryGatewayFilterFactory();
}
//......
}
RetryGatewayFilterFactory is enabled by default
RetryGatewayFilterFactory
spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java
public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<RetryGatewayFilterFactory.RetryConfig> {
private static final Log log = LogFactory.getLog(RetryGatewayFilterFactory.class);
public RetryGatewayFilterFactory() {
super(RetryConfig.class);
}
@Override
public GatewayFilter apply(RetryConfig retryConfig) {
retryConfig.validate();
Predicate<? super RepeatContext<ServerWebExchange>> predicate = context -> {
ServerWebExchange exchange = context.applicationContext();
if (exceedsMaxIterations(exchange, retryConfig)) {
return false;
}
HttpStatus statusCode = exchange.getResponse().getStatusCode();
HttpMethod httpMethod = exchange.getRequest().getMethod();
boolean retryableStatusCode = retryConfig.getStatuses().contains(statusCode);
if (!retryableStatusCode && statusCode != null) { // null status code might mean a network exception?
// try the series
retryableStatusCode = retryConfig.getSeries().stream()
.anyMatch(series -> statusCode.series().equals(series));
}
boolean retryableMethod = retryConfig.getMethods().contains(httpMethod);
return retryableMethod && retryableStatusCode;
};
Repeat<ServerWebExchange> repeat = Repeat.onlyIf(predicate)
.doOnRepeat(context -> reset(context.applicationContext()));
//TODO: support timeout, backoff, jitter, etc... in Builder
Predicate<RetryContext<ServerWebExchange>> retryContextPredicate = context -> {
if (exceedsMaxIterations(context.applicationContext(), retryConfig)) {
return false;
}
for (Class<? extends Throwable> clazz : retryConfig.getExceptions()) {
if (clazz.isInstance(context.exception())) {
return true;
}
}
return false;
};
Retry<ServerWebExchange> reactorRetry = Retry.onlyIf(retryContextPredicate)
.doOnRetry(context -> reset(context.applicationContext()))
.retryMax(retryConfig.getRetries());
return apply(repeat, reactorRetry);
}
public boolean exceedsMaxIterations(ServerWebExchange exchange, RetryConfig retryConfig) {
Integer iteration = exchange.getAttribute("retry_iteration");
//TODO: deal with null iteration
return iteration != null && iteration >= retryConfig.getRetries();
}
public void reset(ServerWebExchange exchange) {
//TODO: what else to do to reset SWE?
exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_ALREADY_ROUTED_ATTR);
}
@Deprecated
public GatewayFilter apply(Repeat<ServerWebExchange> repeat) {
return apply(repeat, Retry.onlyIf(ctxt -> false));
}
public GatewayFilter apply(Repeat<ServerWebExchange> repeat, Retry<ServerWebExchange> retry) {
return (exchange, chain) -> {
log.trace("Entering retry-filter");
int iteration = exchange.getAttributeOrDefault("retry_iteration", -1);
exchange.getAttributes().put("retry_iteration", iteration + 1);
return Mono.fromDirect(chain.filter(exchange)
.log("retry-filter", Level.INFO)
.retryWhen(retry.withApplicationContext(exchange))
.repeatWhen(repeat.withApplicationContext(exchange)));
};
}
//......
}
- It can be seen that this filter uses the Retry component of reactor and adds retry_iteration to the attribues of exchange to record the number of retri es. this value starts from -1 by default, and when it is first executed, retry_iteration+1 is 0. After each retry, add 1.
- Filter’s application receives two parameters, one is Repeat<ServerWebExchange > and the other is Retry<ServerWebExchange >.
- The difference between repeat and retry is that repeat retries when it is onCompleted, while retry retries when it is onError. Since it is not always possible to try again when it is abnormal, repeat is added here.
RetryConfig
spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java
public static class RetryConfig {
private int retries = 3;
private List<Series> series = toList(Series.SERVER_ERROR);
private List<HttpStatus> statuses = new ArrayList<>();
private List<HttpMethod> methods = toList(HttpMethod.GET);
private List<Class<? extends Throwable>> exceptions = toList(IOException.class);
//......
public void validate() {
Assert.isTrue(this.retries > 0, "retries must be greater than 0");
Assert.isTrue(!this.series.isEmpty() || !this.statuses.isEmpty(),
"series and status may not both be empty");
Assert.notEmpty(this.methods, "methods may not be empty");
}
//......
}
You can see that the configuration file has 5 attributes, as follows:
- Retries, which is 3 by default, identifies the number of retries
- Series, used to specify which segments of the status code need to be retried, the default SERVER_ERROR is 5xx
- Statuses, used to specify which states need to be retried. The default is blank. It must specify at least one with series.
- Methods, used to specify that requests for those methods need to be retried; the default is GET
- Exceptions, used to specify which exceptions need to be retried, default is java.io.IOException
Example
spring:
cloud:
gateway:
routes:
- id: retry-demo
uri: http://localhost:9090
predicates:
- Path=/retry/**
filters:
- name: Retry
args:
retries: 15
series:
- SERVER_ERROR
- CLIENT_ERROR
methods:
- GET
- POST
exceptions:
- java.io.IOException
- java.util.concurrent.TimeoutException
Here, the GET or POST methods for 4xx and 5xx, or IOException or TimeoutException are specified for retry, with the number of retries being 15.
Summary
RetryGatewayFilter retried with the help of the retry component of reactor-addons, mainly using Mono’s repeatWhen and retryWhen methods, the former triggered when onCompleted and the latter when onError.