文章目录
- 1. 简介
- 2. 引入重试机制
- 3. 补偿策略
- 4. 配置说明
- 5. zuul 中的使用
- 6. 总结
写在前面
文章参考 Spring-cloud-netflix 的官方文档。
1. 简介
Spring Cloud Netflix
提供了多种方式来发起 HTTP 请求。我们能够使用 负载均衡的 RestTemplate
,Ribbon,或者 Feign 。无论选择何种方式发起请求,都有可能面临失败。这种情况下,我们通常希望能够自动发起重试。想要使 Spring Cloud netflix
做到这个,我们需要在应用程序的类路径下引入 Spring Retry
。当它存在时,并且我们不修改默认配置时,负载均衡的 RestTemplate
,feign 以及 zuul 都将自动重试失败的请求。
# 关闭自动重试,默认为 true
spring.cloud.loadbalancer.retry.enabled = false
这里需要注意的是 引入 feign 时,以上配置参数并不会生效。我的代码运行起来是这样的,跟踪源码确认了这个现象 。这个时候的超时设置:
ribbon:
ConnectTimeout: 2000
ReadTimeout: 2000
maxAutoRetries: 2
这些都会生效。当请求超时时,会自动重试,跟踪代码发现其采用的是 RetryableFeignLoadBalancer
均衡器。在其 execute
方法中入了 retryTemplate
。
如果引入 ribbon,hystrix 时,会发现上诉配置不会生效(至少我的代码运行起来是这样的)。跟踪源码发现,retryTemplate
是在 restTemplate
的 RetryLoadBalancerInterceptor
拦截器中加入的。
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
final String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
final LoadBalancedRetryPolicy retryPolicy = this.lbRetryFactory
.createRetryPolicy(serviceName, this.loadBalancer);
RetryTemplate template = createRetryTemplate(serviceName, request, retryPolicy);
return template.execute(context -> {
ServiceInstance serviceInstance = null;
if (context instanceof LoadBalancedRetryContext) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
serviceInstance = lbContext.getServiceInstance();
}
if (serviceInstance == null) {
serviceInstance = this.loadBalancer.choose(serviceName);
}
// 超时并不会抛出异常,依然会正常响应
ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer
.execute(serviceName, serviceInstance,
this.requestFactory.createRequest(request, body, execution));
int statusCode = response.getRawStatusCode();
if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody());
response.close();
throw new ClientHttpResponseStatusCodeException(serviceName, response,
bodyCopy);
}
return response;
}, new LoadBalancedRecoveryCallback<ClientHttpResponse, ClientHttpResponse>() {
// This is a special case, where both parameters to
// LoadBalancedRecoveryCallback are
// the same. In most cases they would be different.
@Override
protected ClientHttpResponse createResponse(ClientHttpResponse response,
URI uri) {
return response;
}
});
}
通过源码发现:
ribbon:
retryableStatusCodes: 500
这个配置是会生效的。我也进行了测试,结果确实是这样。如果以上是因为我的配置原因导致,还望通知我一下。
# hystrix 的超时设置 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=30000 引入 ribbon 和 hystrix 时,我的执行代码: @HystrixCommand(fallbackMethod = "helloFallback") public String hello(){ return restTemplate.getForEntity("http://HELLO-SERVICE/provider/hello", String.class).getBody(); } 引入 feign 时,采用的是 @FeignClient 注解。
2. 引入重试机制
通过加入以下依赖即可:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
3. 补偿策略
默认情况下,重试请求并没有使用回退策略和重试监听器。如果需要配置,则需要创建一个 LoadBalancerRetryFactory
类型的 bean,并覆盖 createBackOffPolicy
和 createRetryListeners
方法。
@Configuration
public class MyConfiguration {
@Bean
LoadBalancedRetryFactory retryFactory() {
return new LoadBalancedRetryFactory() {
@Override
public RetryListener[] createRetryListeners(String service) {
return new RetryListener[0];
}
@Override
public BackOffPolicy createBackOffPolicy(String service) {
return new ExponentialBackOffPolicy();
}
};
}
}
// 或者继承 RibbonLoadBalancedRetryFactory ,重写以上两个接口。
4. 配置说明
特殊情况,我已在简介中说明,当然,我希望你结合代码来验证这些情况。
当使用 ribbon 时,具体的配置参考 ribbon 文档。
此外,可能希望在响应中返回某些状态码时重试请求。可以通过设置 clientName.ribbon.retryableStatusCodes
来列出希望Ribbon客户端重试的响应代码。如下例所示:
clientName:
ribbon:
retryableStatusCodes: 404,502
也可以创建LoadBalancedRetryPolicy
类型的bean,并实现 retryableStatusCode
方法来重试给定状态代码的请求。以上这些代码都可以通过跟踪源码的方式找到具体的应用之处。
5. zuul 中的使用
通过 zuul.retryable = false
来关闭重试功能。也能够禁用指定路由上的重试功能:
zuul.routes.routename.retryable = false
6. 总结
按照文档的配置,但并未得到文档所说的结果,我无法论证是版本的问题(我看的是对应版本的文档)还是怎样。通过跟踪源码发现,确实是不能得到那样的结果。或许,我还是少了什么配置,但至少在目前,我是无法发现的。
这里还发现一个有趣的现象,当使用 hystrix 时(假设我们在服务端使用线程休眠,但最后仍然能够返回正确的结果),如果发生超时,从重试负载均衡器器的执行结果来看,任然能够得到正确的结果,只是被丢弃掉了。并且触发了 HystrixCommand
的 fallbackMethod
指定的方法 ,想来这也是合理的。