聊聊spring cloud gateway的RetryGatewayFilter

本文主要研究一下spring cloud gateway的RetryGatewayFilterhtml

GatewayAutoConfiguration

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();
    }
    //......
}
默認啓用了RetryGatewayFilterFactory

RetryGatewayFilterFactory

spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.javareact

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)));
        };
    }
    //......
}
  • 能夠看到這個filter使用了reactor的Retry組件,同時往exchange的attribues添加retry_iteration,用來記錄重試次數,該值默認從-1開始,第一次執行的時候,retry_iteration+1爲0。以後每重試一次,就添加1。
  • filter的apply接收兩個參數,一個是Repeat<ServerWebExchange>,一個是Retry<ServerWebExchange>。
  • repeat與retry的區別是repeat是在onCompleted的時候會重試,而retry是在onError的時候會重試。這裏因爲不必定是異常的時候纔可能重試,因此加了repeat。

RetryConfig

spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.javaspring

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個屬性,以下:
  • retries,默認爲3,用來標識重試次數
  • series,用來指定哪些段的狀態碼須要重試,默認SERVER_ERROR即5xx
  • statuses,用於指定哪些狀態須要重試,默認爲空,它跟series至少得指定一個
  • methods,用於指定那些方法的請求須要重試,默認爲GET
  • exceptions,用於指定哪些異常須要重試,默認爲java.io.IOException

實例

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次。

小結

RetryGatewayFilter藉助了reactor-addons的retry組件進行了重試,主要使用了Mono的repeatWhen及retryWhen方法,前者在onCompleted的時候觸發,後者在onError的時候觸發。segmentfault

doc

相關文章
相關標籤/搜索