刚学Spring Cloud时,被里面的各种组件的超时搞得晕头转向的,所以趁着这段不忙的时间,查找了不少的资料,测试了一些代码,好好总结一下,避免以后忘记掉。
这里Spring Cloud的版本为Greenwich.SR1,代码都是最简单的写法,主要是为了验证各种超时配置。
刚学Spring Cloud,我是通过网上的各种博客教程学习,跟着示例敲代码,下面就是一个ribbon的示例代码:
eureka-client服务:
@RequestMapping("/test")
public String test() {
int sleepTime = new Random().nextInt(4000);
System.out.println("sleepTime: " + sleepTime);
try {
Thread.sleep(sleepTime); //模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
return "test";
}
eureka-client就是一个简单的服务,提供一个test的接口供ribbon远程调用。
service-ribbon服务:
启动类:
@SpringBootApplication
public class RibbonApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
controller:
@RestController
public class TestController {
private final TestService testService;
@Autowired
public TestController(TestService testService) {
this.testService = testService;
}
@GetMapping("/test")
public String test() {
return testService.test();
}
}
service:
@Service
public class TestService {
public final RestTemplate restTemplate;
@Autowired
public TestService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String test() {
return restTemplate.getForObject("http://eureka-client/test", String.class);
}
}
代码很简单,也没其他的配置,当然一些基本的配置比如注册到eureka server等配置还有依赖我这里就不列出来了。
跑一下:
可以看到正常返回信息了,但我发现模拟的网络延迟3秒多了还是能正常返回,好奇之下我试了下10秒延迟:
什么情况,10秒都能正常返回,难道默认没有超时吗?带着这个疑虑,我在service-ribbon服务的yml配置文件中给ribbon设置了一个超时:
ribbon:
ReadTimeout: 2000
ConnectionTimeout: 2000
重试了一下发现10秒还是能正常返回,不会超时,嗯?怎么回事?上网搜ribbon超时发现都是这样配置的,难道是新版本改了?我看了下文档好像也不是这么一回事(虽然英语不好)。我又重新上网查找资料,最后发现RestTemplate使用默认构造函数时是使用SimpleClientHttpRequestFactory来创建http请求的,通过debug也可知:
而查看SimpleClientHttpRequestFactory源码可知SimpleClientHttpRequestFactory默认是没有超时的:
public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
private static final int DEFAULT_CHUNK_SIZE = 4096;
@Nullable
private Proxy proxy;
private boolean bufferRequestBody = true;
private int chunkSize = DEFAULT_CHUNK_SIZE;
private int connectTimeout = -1;
private int readTimeout = -1;
private boolean outputStreaming = true;
@Nullable
private AsyncListenableTaskExecutor taskExecutor;
...
}
这也解释了为什么上面延迟10秒都不会超时。而要想设置超时,就需要对SimpleClientHttpRequestFactory实例设置超时时间并注入到RestTemplate中:
@SpringBootApplication
public class RibbonApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(2000);
requestFactory.setReadTimeout(2000);
return new RestTemplate(requestFactory);
}
}
此时,如果超时,就会报错:
如果想使ribbon.ConnectTimeout ,ribbon.ReadTimeout这两个参数配置生效,则需要多一个配置:
ribbon:
ReadTimeout: 2000
ConnectionTimeout: 2000
http:
client:
enabled: true
把ribbon.http.client.enabled设置为true,就会使用RibbonClientHttpRequestFactory,此时ribbon.ConnectTimeout ,ribbon.ReadTimeout 这两个参数配置才会生效
而如果不配超时时间,默认超时时间是1秒,超时后默认会重试一次。
ribbon:
ReadTimeout: 2000
ConnectionTimeout: 2000
OkToRetryOnAllOperations: true
MaxAutoRetriesNextServer: 1 # 当前实例全部失败后可以换1个实例再重试
MaxAutoRetries: 2 # 在当前实例只重试2次
http:
client:
enabled: true
以上配置最多会调用6次((MaxAutoRetries+1)*(MaxAutoRetriesNextServer+1)),也就是最多会重试5次。
eureka-client还是上面的例子没变化,主要看service-feign服务:
controller:
@RestController
public class TestController {
private final TestFeign testFeign;
@Autowired
public TestController(TestFeign testFeign) {
this.testFeign = testFeign;
}
@GetMapping("/test")
public String test() {
return testFeign.test();
}
}
feign接口:
@FeignClient("eureka-client")
public interface TestFeign {
@RequestMapping("/test")
String test();
}
配置文件还是基本的配置。
运行如下:
feign默认的超时时间是1秒,重试1次。在我查找资料的过程当中,发现一些博客是写feign默认重试5次(包括首次),但在我的测试中都不会出现重试5次的情况,几经搜查,在一篇大佬博客中找到答案:由于feign本身也具备重试能力,在早期的Spring Cloud中,Feign使用的是 feign.Retryer.Default#Default() ,而它默认重试5次。
public interface Retryer extends Cloneable {
/**
* if retry is permitted, return (possibly after sleeping). Otherwise propagate the exception.
*/
void continueOrPropagate(RetryableException e);
Retryer clone();
public static class Default implements Retryer {
private final int maxAttempts;
private final long period;
private final long maxPeriod;
int attempt;
long sleptForMillis;
public Default() {
this(100, SECONDS.toMillis(1), 5);
}
public Default(long period, long maxPeriod, int maxAttempts) {
this.period = period;
this.maxPeriod = maxPeriod;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
...
}
但Feign整合了Ribbon,Ribbon也有重试的能力,此时,就可能会导致行为的混乱。所以C版本及以后的版本重试机制改为feign.Retryer#NEVER_RETRY,即关闭了feign自身的重试机制。
/**
* Implementation that never retries request. It propagates the RetryableException.
*/
Retryer NEVER_RETRY = new Retryer() {
@Override
public void continueOrPropagate(RetryableException e) {
throw e;
}
@Override
public Retryer clone() {
return this;
}
};
如需使用重试,只需使用Ribbon的重试配置,超时设置也是配ribbon的,即
ribbon:
ReadTimeout: 2000
ConnectionTimeout: 2000
OkToRetryOnAllOperations: true
MaxAutoRetriesNextServer: 1
MaxAutoRetries: 2
feign的超时设置其实还有一层Hystrix的,这个留下一篇再讲。
demo地址
参考链接:
Spring Cloud各组件重试总结
feign 的重试机制