聊聊openfeign的超时和重试
openfeign是⼀种声明式的http客户端,它可以⽅便地集成到springcloud,像调⽤本地⽅法⼀样使⽤http⽅式调⽤远程服务。今天我们来聊⼀聊feign的超时和重试。
构建环境
注:本⽂使⽤的openfeign版本:2.1.0.RELEASE
在pom⽂件中增加下⾯配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
openfeign默认使⽤java原⽣的URLConnection。这⾥我们要选择⼀个http的客户端,⽐如选择apache的,需要在
application.properties⽂件中增加下⾯这个配置:
abled=true
同时需要在pom⽂件中引⼊下⾯这个jar包:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>9.3.1</version>
</dependency>
我们也可以选择okhttp的,这时需要在application.properties⽂件中增加下⾯这个配置:
abled=true
同时需要在pom⽂件中进⼊okhttp的jar包:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>10.2.0</version>
</dependency>
在我本地的实验中,有⼀个服务叫springboot-mybatis,eureka地址是localhost:8889,在application.properties⽂件中加⼊下⾯配置:
eureka.instance.hostname=localhost
eureka.instance.prefer-ip-address=true
eureka.client.serviceUrl.defaultZone=${eureka.instance.hostname}:8889/eureka/
boot启动类加上下⾯这个注解:
@EnableFeignClients
这样就初步实现了⼀个openfeign的配置,我在服务端⼯程(springboot-mybatis)中增加了⼀个⽅法,让客户端来调⽤,代码如下:
@Controller
@RequestMapping("/feign")
public class FeignTestController {
@RequestMapping("/feignReadTimeout")
@ResponseBody
public String getEmployeebyName() throws InterruptedException {
//这⾥配置10s的超时时间,给后⾯的实验⽤
Thread.currentThread().sleep(10000);
return "success";
}
}
在feign客户端的调⽤代码如下:
@FeignClient("springboot-mybatis")
public interface FeignAsEurekaClient {
@GetMapping("/feign/feignReadTimeout")
String feignReadTimeout();
}
超时配置
注:下⾯的实验使⽤的是okhttp来进⾏的。
上⾯实现了⼀个简单的feign使⽤demo,不过feign的使⽤还有很多需要注意的地⽅,这⾥我们来聊⼀聊超时。先看第⼀种情况,feign客户端和服务端都注册在⼀个eureka的情况。
1.不配置超时时间,默认"读超时60s"
上⾯的demo我们没有设置超时时间,所以虽然服务端响应延迟10s,请求还是能成功的。
但是上⾯的"读超时60s"我加了引号,为什么呢?在feign.Request⾥⾯有⼀个内部类,如果不配置超时,外部会调⽤下⾯这个构造函数,连接超时10s,读超时60s
public Options() {
this(10 * 1000, 60 * 1000);
}
如果我们没有配置feign超时时间,上⾯的时间也会被ribbon覆盖?请求连接时间和超时时间,默认为1秒,在RibbonClientConfiguration类定义,被覆盖后也是会读超时的。
覆盖超时时间设置的代码在FeignLoadBalancer,代码如下:
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(
}
else {
options = new Request.tTimeout, adTimeout);
}
Response response = request.client().Request(), options);
return new Uri(), response);
}
所以我们加⼊下⾯的配置,把ribbon的读超时时间调⼤,也是可以解决读超时问题的:
abled=true
#ribbon.ConnectTimeout=2000
#请求处理的超时时间
ribbon.ReadTimeout=10000
但ribbon是⼀个做负载均衡的,我们还是给feign定义超时时间⽐较好。因为feign配置了超时时间后,会最后赋值给Options的超时时间。在FeignClientFactoryBean类的configureUsingProperties⽅法。
if (ConnectTimeout() != null && ReadTimeout() != null) {
builder.options(new Request.ConnectTimeout(), ReadTimeout()));
}
2.配置⼀个默认超时时间
tTimeout=2000
adTimeout=5000
这⾥我配置了连接超时是2s,读取超时是5s,这样,上⾯demo中的请求就失败了,要想成功,readTimeout不能低于10000。
3.为单个服务设置超时时间
如果我们有⼀个接⼝超时时间很长,要全局都设置⼀个这么长的超时时间吗?这样会有问题,⼀个平时响应很快的接⼝,如果服务端出故障了,我们应该让它fail-fast。这样我们就需要对慢的接⼝或服务单独设置超时时间。
对某⼀个服务设置单独的超时时间,配置如下:
#对单个服务设置超时,会覆盖默认的超时
tTimeout=2000
adTimeout=11000
4.为单个接⼝设置超时时间
还是上⾯的问题,⼀个服务有多个接⼝,只有⼀个超级慢,那对整个服务的所有接⼝设置⼀个超时时间也会影响其他接⼝的fail-fast,我们需要对单个接⼝设置超时时间,就得配合hystrix来配置了,配置如下:
#开启熔断
#abled=true
#开启超时熔断,默认为true,如果为false,则熔断机制只在服务不可⽤时开启,这个配置不加,使⽤默认配置
#abled=false
#如果上⾯⼀个配置是true,设置超时熔断时间,因为openfeign中的熔断时间默认是1s,太短了,我们必须⼿⼯设置⼀个
#ution.isolation.thread.timeoutInMilliseconds=15000
#下⾯的配置为单个接⼝设置超时时间
#xxx:要设置的某个FeignClient的类名
#yyy:⽅法名
#zzz:参数,如果是基础类型,就直接填基础类型:String/int;如果是某个对象,就直接填对象的类名
##yyy(zzz).execution.isolation.thread.timeoutInMilliseconds=1000
#hystrixmand.FeignAsHttpCient#feignReadTimeout().execution.isolation.thread.timeoutInMilliseconds=23000
这⾥必须注意⼀下,ribbon的读超时时间也不能⼩于接⼝的返回时间,不然回报ribbon超时,所以ribbon配置如下:
ribbon.ConnectTimeout=2000
ribbon.ReadTimeout=11000
这⾥我必须说明⼀下,如果配置了feign的超时时间,并且feign的读超时不够,熔断的超时时间是不起
作⽤的。坑爹啊。原因是什么呢?有待研究。
原因:这⾥hystrix的时间其实并没有起作⽤,起作⽤的其实还是feign的超时时间
那不是说openfeign如果给单个服务设置了超时时间,或设置了默认超时时间,就不能给单个响应慢的接⼝设置超时时间了吗?
下⾯我们看第⼆种情况,使⽤feign作为http客户端来调⽤外部的服务情况。这种情况的客户端我也给出⼀个,服务的接⼝还是刚刚那个:
@FeignClient(name = "feign", url = "localhost:8083")
public interface FeignAsHttpCient {
@GetMapping("/feign/feignReadTimeout")
String feignReadTimeout();
}
这⾥我们需要在application.properties⽂件中增加下⾯这个配置就可以了:
hystrixmand.FeignAsHttpCient#feignReadTimeout().execution.isolation.thread.timeoutInMilliseconds=11000
注意:这⾥hystrix的时间其实并没有起作⽤,起作⽤的其实还是feign的超时时间
那对单个接⼝怎么设置超时时间呢,我给出⼀个参考,如下:
public class RestTemplateConfiguration {
@Bean
public OkHttp3ClientHttpRequestFactory okHttp3RequestFactory(){
OkHttp3ClientHttpRequestFactory requestFactory = new OkHttp3ClientHttpRequestFactory();
//注意:这⾥配置了超时时间,就不受ribbon超时时间的影响了
requestFactory.setConnectTimeout(2000);
requestFactory.setReadTimeout(15000);
return requestFactory;
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(OkHttp3ClientHttpRequestFactory okHttp3RequestFactory){
return new RestTemplate(okHttp3RequestFactory);
}
}
如果作为eureka客户端,需要加@LoadBalanced的注解,发送请求代码如下,springboot-mybatis是请求的服务名:String response = ForObject("springboot-mybatis/feign/feignReadTimeout", String.class);
如果作为普通http客户端,不能加@LoadBalanced的注解,发送请求代码如下:
String response = ForObject("localhost:8083/feign/feignReadTimeout", String.
class);
重试配置
如果不配置,openfeign默认是不重试的,看FeignClientsConfiguration中的代码:
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
再看⼀下Retryer中的NEVER_RETRY定义:
/**
* Implementation that never retries request. It propagates the RetryableException.timeout on t2 timer
*/
Retryer NEVER_RETRY = new Retryer() {
@Override
public void continueOrPropagate(RetryableException e) {
throw e;
}
@Override
public Retryer clone() {
return this;
}
};
下⾯我给出⼀个重试的配置:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。