Spring Cloud Gateway с Эврикой, получающей 504 (Тайм-аут шлюза)

Предположим, у меня есть один Eureka Server, один Spring Cloud Gateway и один ServiceA, работающий с двумя экземплярами.

Когда я разверну обновленную версию службы, в течение короткого периода времени eureka будет хранить четыре экземпляра для ServiceA: те, которые больше не существуют, и те, которые только что были запущены.

Пока Эврика не выселит два экземпляра, которые больше не действительны, на шлюзе лента будет по-прежнему распределять нагрузку между теми, которые больше не существуют, генерируя ConnectTimeoutException и следовательно 504 (Gateway Time-out), Я настроил эти маршруты в шлюзе со следующей конфигурацией повторных попыток.

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

И это позволяет ленте повторить попытку немедленно, когда исключение ConnectException: Connection refused но это не повторится, когда это ConnectTimeoutException,

Я мог бы настроить интервалы обновления для клиентов ленты и eureka, но я бы не стал их трогать.

Итак, у меня есть два вопроса по этому поводу.

  • Как я могу зафиксировать таймаут внутри фильтра?
  • Есть ли лучший способ справиться с этим, чтобы достичь нулевого времени простоя?

Спасибо

1 ответ

Пытаться ribbon.retryableStatusCodes=404,502,504


Обновить:

Сначала в моей голове, ConnectException должен ассоциироваться с кодом 502 а также 504 является SocketTimeoutException, Пожалуйста, поправьте меня, если ошибаетесь.

Извините, не заглядывайте больше в облачный шлюз, но его LB может по желанию использовать Ribbon.

Давайте соберем, что вы используете ленту и OKHttp с помощью ribbon.OkHttp.enabled,

OkHttpRibbonConfiguration будет инициировать OkHttpLoadBalancingClient bean, который выполняет запрос. В своем execute(), это создает RetryCallback анонимный объект реализации сначала при каждом выполнении запроса. RetryCallback отвечает за выполнение запроса и повторное выполнение.

Давайте перейдем к логике RetryCallback, После получения ответа он проверяет код состояния ответа, если повторяется или нет. Если RetryTemplate класс существует в загрузчике классов, реализация loadBalancedRetryPolicyFactory является RibbonLoadBalancedRetryPolicyFactory, Он используется для создания RibbonLoadBalancedRetryPolicy объект (retryPlicy ниже) .

executeWithRetry() создать RetryTemplate с RetryPolicy, (предварительное условие: повторная попытка включена в весеннем облачном шлюзе путем установки запроса на повторную попытку) .

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 который расширяется RetryableStatusCodeException, А также RibbonRecoveryCallback ловит это исключение, проверка исключения является реализацией RetryableStatusCodeException или нет. Если да, то RetryTemplate продолжает повторную попытку. Или, бросьте безвозвратное Expcetion, чтобы прервать повторную попытку.

Другие вопросы по тегам