带有 Eureka 的 Spring Cloud Gateway 得到 504(网关超时)
Posted
技术标签:
【中文标题】带有 Eureka 的 Spring Cloud Gateway 得到 504(网关超时)【英文标题】:Spring Cloud Gateway with Eureka getting 504 (Gateway Time-out) 【发布时间】:2018-11-25 18:57:17 【问题描述】:假设我有一个 Eureka Server、一个 Spring Cloud Gateway 和一个运行两个实例的 ServiceA。
当我部署该服务的更新版本时,在短时间内,eureka 将为 ServiceA 保存四个实例,一个不再存在,一个刚刚启动。
在 Eureka 驱逐不再有效的两个实例之前,在网关上,Ribbon 仍将在不再存在的实例之间进行负载平衡,从而生成 ConnectTimeoutException
并因此生成 504 (Gateway Time-out)
。我已经使用以下 retry 配置在网关中配置了这些路由。
val retry = RetryGatewayFilterFactory.RetryConfig()
.setExceptions(ConnectException::class.java)
这允许功能区在异常为 ConnectException: Connection refused
时立即重试,但在 ConnectTimeoutException
时不会重试。
我可以调整功能区和 eureka 客户端的刷新间隔,但我宁愿不碰这些。
所以,我对此有两个问题。
如何在过滤器中捕获超时? 是否有更好的方法来处理此问题以实现零停机时间?谢谢
【问题讨论】:
你在使用 Hystrix 吗? @spencergibb 还没有,我应该这样做吗? 【参考方案1】:试试ribbon.retryableStatusCodes=404,502,504
更新:
首先在我看来,ConnectException
应该与代码 502
相关联,而 504
是 SocketTimeoutException
。如有错误请指正。
抱歉,云网关没有深入研究,但是它的LB可以选择使用Ribbon。
假设您使用 Ribbon 和 OKHttp
via ribbon.OkHttp.enabled
。
OkHttpRibbonConfiguration
将初始化一个 OkHttpLoadBalancingClient
bean,它执行请求。在其execute()
中,它在每次请求执行期间首先创建一个RetryCallback
匿名实现对象。 RetryCallback
负责执行请求和重试执行。
我们来看看RetryCallback
的逻辑。收到响应后,它会检查响应状态码是否可重试。如果类加载器中存在RetryTemplate
类,则loadBalancedRetryPolicyFactory
的实现为RibbonLoadBalancedRetryPolicyFactory
。它用于创建RibbonLoadBalancedRetryPolicy
对象(以下retryPlicy
)。
executeWithRetry()
用RetryPolicy
创建一个RetryTemplate
。 (前提条件,通过将请求设置为可重试,在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);
如果包含,则抛出扩展RetryableStatusCodeException
的OkHttpStatusCodeException
。并且RibbonRecoveryCallback
捕捉到这个异常,检查异常是否是RetryableStatusCodeException
的实现。如果是,则RetryTemplate
继续重试。或者,抛出不可重试的 Expcetion 来中断重试。
【讨论】:
尝试:解释你的答案。 @Anthony,最后一个答案来自手机。只需追加更多。以上是关于带有 Eureka 的 Spring Cloud Gateway 得到 504(网关超时)的主要内容,如果未能解决你的问题,请参考以下文章
带有临时端口的 Spring-cloud brixton、eureka 和 docker
带有 Spring Cloud 和 Eureka java.lang.IllegalStateException 的功能区:没有可用于 localhost 的实例