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, чтобы прервать повторную попытку.