Eureka的Spring Cloud Gateway获得504(网关超时)

时间:2018-06-15 23:49:13

标签: spring-cloud netflix-eureka spring-cloud-netflix netflix-ribbon

假设我有一个 Eureka Server ,一个 Spring Cloud Gateway ,一个 ServiceA 运行两个实例。

当我部署服务的更新版本时,eureka会在短时间内为 ServiceA 保留四个实例,不再存在的实例和刚刚启动的实例。< / p>

在Eureka驱逐网关上不再有效的两个实例之前,功能区仍将在不再存在的实例之间进行平衡,从而生成ConnectTimeoutException,从而生成504 (Gateway Time-out)。我已使用以下重试配置在网关中配置了这些路由。

val retry = RetryGatewayFilterFactory.RetryConfig()
                 .setExceptions(ConnectException::class.java)

这允许功能区在异常为ConnectException: Connection refused时立即重试,但在ConnectTimeoutException时不会重试。

我可以调整功能区和尤里卡客户端的刷新间隔,但我宁愿不接触它们。

所以,我有两个问题。

  • 如何捕获过滤器内的超时?
  • 有没有更好的方法来解决这个问题,以实现零停机时间?

由于

1 个答案:

答案 0 :(得分:1)

尝试ribbon.retryableStatusCodes=404,502,504


更新:

我首先想到ConnectException应该与代码502关联,并且504SocketTimeoutException。如果有错,请纠正我。

对不起,没有更多地关注云网关,但是它的LB可以选择使用Ribbon。

假设您正在通过OKHttp使用Ribbon和ribbon.OkHttp.enabled

OkHttpRibbonConfiguration将初始化一个OkHttpLoadBalancingClient bean,该bean执行请求。在其execute()中,它在每个请求执行期间首先创建一个RetryCallback匿名实现对象。 RetryCallback负责执行请求并重试执行。

让我们看一下RetryCallback的逻辑。收到响应后,它将检查响应状态代码是否可重试。如果类加载器中存在RetryTemplate类,则loadBalancedRetryPolicyFactory的实现为RibbonLoadBalancedRetryPolicyFactory。用来创建RibbonLoadBalancedRetryPolicy对象(下面的retryPlicy)。

executeWithRetry()RetryTemplate创建一个RetryPolicy。 (前提条件是,通过将请求设置为可重试,在Spring Cloud Gateway中启用了重试)。

public OkHttpRibbonResponse execute(...) throws Exception {
    final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
    ...
    final Request request = newRequest.toRequest();
    Response response = httpClient.newCall(request).execute();
    if(retryPolicy.retryableStatusCode(response.code())) {
        ResponseBody responseBody = response.peekBody(Integer.MAX_VALUE);
        response.close();
        throw new OkHttpStatusCodeException(RetryableOkHttpLoadBalancingClient.this.clientName,
                            response, responseBody, newRequest.getURI());
    }
    return new OkHttpRibbonResponse(response, newRequest.getUri());        
}    

private OkHttpRibbonResponse executeWithRetry(...) throws Exception {
    RetryTemplate retryTemplate = new RetryTemplate();
    BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
    retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
    RetryListener[] retryListeners = this.loadBalancedRetryListenerFactory.createRetryListeners(this.getClientName());
    if (retryListeners != null && retryListeners.length != 0) {
        retryTemplate.setListeners(retryListeners);
    }
    boolean retryable = isRequestRetryable(request); //HERE
    retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy()
            : new RetryPolicy(request, retryPolicy, this, this.getClientName()));
    return retryTemplate.execute(callback, recoveryCallback);
}

private boolean isRequestRetryable(ContextAwareRequest request) {
    return request.getContext() == null ? true :
        BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true);
}

retryableStatusCode()中,它检查retryableStatusCodes是否包含响应状态代码。

public class RibbonLoadBalancedRetryPolicy implements LoadBalancedRetryPolicy {
    public static final IClientConfigKey<String> RETRYABLE_STATUS_CODES = new CommonClientConfigKey<String>("retryableStatusCodes") {};
    ....
    List<Integer> retryableStatusCodes = new ArrayList<>();
        public RibbonLoadBalancedRetryPolicy(String serviceId, RibbonLoadBalancerContext context, ServiceInstanceChooser loadBalanceChooser,
                                         IClientConfig clientConfig) {
        this.serviceId = serviceId;
        this.lbContext = context;
        this.loadBalanceChooser = loadBalanceChooser;
        String retryableStatusCodesProp = clientConfig.getPropertyAsString(RETRYABLE_STATUS_CODES, "");
        String[] retryableStatusCodesArray = retryableStatusCodesProp.split(",");
        for(String code : retryableStatusCodesArray) {
            if(!StringUtils.isEmpty(code)) {
                try {
                    retryableStatusCodes.add(Integer.valueOf(code.trim()));
                } catch (NumberFormatException e) {
                    //TODO log
                }
            }
        }
    }
    ...
    @Override
    public boolean retryableStatusCode(int statusCode) {
        return retryableStatusCodes.contains(statusCode);
    }
}

如果包含,则抛出扩展OkHttpStatusCodeException的{​​{1}}。并且RetryableStatusCodeException捕获了此异常,检查异常是否是RibbonRecoveryCallback的实现。如果是,则RetryableStatusCodeException继续重试。或者,抛出不可重试的Expection来中断重试。