SpringGateway使用loadbalance支持重试配置

使用webClient进行接口调用,在初始话webClient时添加 @Loadbalance注解

spring 自动装配置就会自动注入默认的filter

类:ReactorLoadBalancerClientAutoConfiguration

会自动注入 webClient 【RetryableLoadBalancerExchangeFilterFunction】 过滤器和重试策略【 RetryableExchangeFilterFunctionLoadBalancerRetryPolicy 重试策略】

    @ConditionalOnMissingBean
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true")
    @Bean
    public RetryableLoadBalancerExchangeFilterFunction retryableLoadBalancerExchangeFilterFunction(
            ReactiveLoadBalancer.Factory loadBalancerFactory,
            LoadBalancerRetryPolicy.Factory retryPolicyFactory,
            ObjectProvider> transformers) {
        return new RetryableLoadBalancerExchangeFilterFunction(retryPolicyFactory, loadBalancerFactory,
                transformers.getIfAvailable(Collections::emptyList));
    }

    @ConditionalOnMissingBean
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true")
    @Bean
    public LoadBalancerRetryPolicy.Factory loadBalancerRetryPolicy(
            ReactiveLoadBalancer.Factory loadBalancerFactory) {
        return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy.Factory(loadBalancerFactory);
    }

当发起http 请求时就会执行org.springframework.cloud.client.loadbalancer.reactive.RetryableLoadBalancerExchangeFilterFunction#filter 方法中

@Override
    public Mono filter(ClientRequest clientRequest, ExchangeFunction next) {
        URI originalUrl = clientRequest.url();
        String serviceId = originalUrl.getHost();
        if (serviceId == null) {
            String message = String.format("Request URI does not contain a valid hostname: %s", originalUrl.toString());
            if (LOG.isWarnEnabled()) {
                LOG.warn(message);
            }
            return Mono.just(ClientResponse.create(HttpStatus.BAD_REQUEST).body(message).build());
        }
        LoadBalancerRetryContext loadBalancerRetryContext = new LoadBalancerRetryContext(clientRequest);
        LoadBalancerProperties properties = loadBalancerFactory.getProperties(serviceId);

        Retry exchangeRetry = buildRetrySpec(properties.getRetry().getMaxRetriesOnSameServiceInstance(), true,
                properties.getRetry());
        Retry filterRetry = buildRetrySpec(properties.getRetry().getMaxRetriesOnNextServiceInstance(), false,
                properties.getRetry());
        LoadBalancerRetryPolicy retryPolicy = retryPolicyFactory.apply(serviceId);

        Set supportedLifecycleProcessors = LoadBalancerLifecycleValidator
                .getSupportedLifecycleProcessors(
                        loadBalancerFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
                        RetryableRequestContext.class, ResponseData.class, ServiceInstance.class);
        String hint = getHint(serviceId, properties.getHint());
        RequestData requestData = new RequestData(clientRequest);
        DefaultRequest lbRequest = new DefaultRequest<>(
                new RetryableRequestContext(null, requestData, hint));
        supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));

        //choose 默认使用轮询算法,选取一个节点
        return Mono.defer(() -> choose(serviceId, lbRequest).flatMap(lbResponse -> {
           //实例信息
            ServiceInstance instance = lbResponse.getServer();
            lbRequest.setContext(new RetryableRequestContext(instance, requestData, hint));
            if (instance == null) {
                String message = serviceInstanceUnavailableMessage(serviceId);
                if (LOG.isWarnEnabled()) {
                    LOG.warn(message);
                }
                supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
                        .onComplete(new CompletionContext(
                                CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
                return Mono.just(ClientResponse.create(HttpStatus.SERVICE_UNAVAILABLE)
                        .body(serviceInstanceUnavailableMessage(serviceId)).build());
            }

            if (LOG.isDebugEnabled()) {
                LOG.debug(String.format("LoadBalancer has retrieved the instance for service %s: %s", serviceId,
                        instance.getUri()));
            }
            LoadBalancerProperties.StickySession stickySessionProperties = properties.getStickySession();
            ClientRequest newRequest = buildClientRequest(clientRequest, instance,
                    stickySessionProperties.getInstanceIdCookieName(),
                    stickySessionProperties.isAddServiceInstanceCookie(), transformers);
            supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, lbResponse));
            return next.exchange(newRequest)
                    //请求执行失败
                    .doOnError(throwable -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
                            .onComplete(new CompletionContext(
                                    CompletionContext.Status.FAILED, throwable, lbRequest, lbResponse))))          //请求执行成功         
                     .doOnSuccess(clientResponse -> supportedLifecycleProcessors.forEach(
                            lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
                                    lbRequest, lbResponse, new ResponseData(clientResponse, requestData)))))
                    .map(clientResponse -> {
                        loadBalancerRetryContext.setClientResponse(clientResponse);
                        //相同节点重试     
                        if (shouldRetrySameServiceInstance(retryPolicy, loadBalancerRetryContext)) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug(String.format("Retrying on status code: %d",
                                        clientResponse.statusCode().value()));
                            }
                            throw new RetryableStatusCodeException();
                        }
                        return clientResponse;

                    });
        }).map(clientResponse -> {
            loadBalancerRetryContext.setClientResponse(clientResponse);
             选取一个新的节点
            if (shouldRetryNextServiceInstance(retryPolicy, loadBalancerRetryContext)) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug(String.format("Retrying on status code: %d", clientResponse.statusCode().value()));
                }
                throw new RetryableStatusCodeException();
            }
            return clientResponse;

        }).retryWhen(exchangeRetry)).retryWhen(filterRetry);
    }

参数详细解:

retryableStatusCodes

默认情况下,Ribbon不会针对HTTP非正常响应状态值(如404、502等)进行重试。如果您需要对特殊的Http状态进行重试,需要配置该参数。

OkToRetryOnAllOperations

这个参数指的是是否允许所有的HTTP请求(GET,POST,PUT等)重试。默认值是false,只允许GET请求重试。对于POST等请求,请慎重使用。

MaxAutoRetries

这个参数用于配置当前实例最大重试次数,默认值为0。重试次数不包括第一次请求。

MaxAutoRetriesNextServer

这个参数指的是切换实例最大重试次数,默认值1。

如果访问当前实例异常,会再次尝试访问当前实例(次数由MaxAutoRetries决定);如果还不行,就会访问下一个实例;如果仍然不行,会把下一个实例作为当前实例并重试(次数由MaxAutoRetries决定)...依此类推,直到切换实例次数达到上限(由MaxAutoRetriesNextServer决定)。总共的重试次数计算公式:

- spring.cloud.loadbalancer.retry.enabled:启用或禁用重试。默认为 false。

- spring.cloud.loadbalancer.retry.repeat-services:是否在同一服务实例上重试。默认为 false。

- spring.cloud.loadbalancer.retry.retryable-status-codes:可重试的HTTP状态码。默认为 500,502,503。

- spring.cloud.loadbalancer.retry.backoff.first-backoff:第一次重试尝试之前等待的时间。默认为 1000ms。

- spring.cloud.loadbalancer.retry.backoff.max-backoff:重试之间等待的最长时间。默认为 2000ms。

- spring.cloud.loadbalancer.retry.backoff.multiplier:退避乘数。默认为 1.1。

重试方案2

添加依赖


org.springframework.retry
spring-retry
1.3.1

添加配置

spring:
  cloud:
    gateway:
      routes:
        - id: my_route
          uri: http://example.com
          predicates:
            - Path=/my/path/**
          filters:
            - RewritePath=/my/path/(?.*), /$\{remaining}
            - name: Retry
              args:
                retries: 3
                statuses: BAD_GATEWAY
                backoff:
                  firstBackoff: 1000ms
                  maxBackoff: 10000ms
                  factor: 2.0
          metadata:
            connectTimeout: 5000
            readTimeout: 10000

为my_route路由配置重试,最多重试3次,仅在收到BAD_GATEWAY响应时重试。配置了指数退避策略,第一次重试之前等待1秒,最多等待10秒,并且每次重试的等待时间是前一次的2倍。

你可能感兴趣的:(spring,boot,spring,gateway)