本文主要研究一下spring cloud gateway的RetryGatewayFilterhtml
spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.javajava
@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(); } //...... }
默認啓用了RetryGatewayFilterFactoryreact
spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.javaspring
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))); }; } //...... }
spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.javasegmentfault
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"); } //...... }
能夠看到配置文件有5個屬性,以下:api
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
這裏指定了針對4xx及5xx的GET或POST方法或者IOException或TimeoutException的時候進行重試,重試次數爲15次。app
RetryGatewayFilter藉助了reactor-addons的retry組件進行了重試,主要使用了Mono的repeatWhen及retryWhen方法,前者在onCompleted的時候觸發,後者在onError的時候觸發。ide