前言
平常的开发工作中调用Rpc服务最关注的性能指标就是响应时间rt,OpenFeign提供了超时时间配置项。本文将从源码层面分析OpenFeign超时时间配置原理,以及Ribbon超时配置的关系分析。
通过本文可以明白两个问题:
- OpenFeign超时配置和Ribbon超时配置的关系
- OpenFeign超时配置能否动态实时修改生效
OpenFeign配置超时方式
在OpenFeign中,有一个超时项配置类,专门用来接收超时配置的。
支持连接超时和读数据超时配置,所有的超时配置最终都会生成对应的Options实例。
public static class Options {
//连接超时时间
private final int connectTimeoutMillis;
//读数据超时时间
private final int readTimeoutMillis;
private final boolean followRedirects;
}
默认多久超时呢?
在FeignRibbonClientAutoConfiguration
自动配置中使用了默认的构造函数,根据构造函数参数,默认连接超时10s,读数据6s超时,也就是不改配置,OpenFeign的接口默认超时时间有10s。
public Options() {
this(10000, 60000);
}
OpenFeign到底有哪些配置方式呢?
- 全局配置方式一
feign.client.config.default.connectTimeout=1000 feign.client.config.default.readTimeout=1000
- 全局配置方式二
@Configuration
public class TestFeignClientConfiguration {
@Bean
public Request.Options options() {
//配置全局300ms就超时
Request.Options options = new Request.Options(300, 300);
return new Request.Options();
}
}
}
-
指定FeignClient生效配置一
以feignClientFeignClientApi
为例
feign.client.config.FeignClientApi.connectTimeout=1000 feign.client.config.FeignClientApi.readTimeout=1000
-
指定FeignClient生效配置二
@FeignClient(value = "fox-server", contextId = "feignClientApi", configuration = TestFeignClientConfiguration.class)
public interface FeignClientApi {
@PostMapping("/get")
String getName(@RequestBody @Validated DemoRequest request);
}
//和全局配置比少了Configuration注解
public class TestFeignClientConfiguration {
@Bean
public Request.Options options() {
//配置全局300ms就超时
Request.Options options = new Request.Options(300, 300);
return new Request.Options();
}
}
}
配置生效原理
这些配置是怎么生效的呢?
首先在Feign的自动装配类FeignAutoConfiguration
中,启用了
FeignClientProperties
配置类。这样配置文件里的属性就可以被解析了。
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
public class FeignAutoConfiguration {
}
配置类以feign.client
开头
@ConfigurationProperties("feign.client")
public class FeignClientProperties {
private boolean defaultToProperties = true;
private String defaultConfig = "default";
private Map config = new HashMap();
在FeignClientConfiguration
中有超时时间字段
这样Spring容器启动后会将feign.client
为前缀的配置项注入到FeignClientProperties类对象中。
由于配置项config
是一个map,只要key是对应的FeignClient里的contextId
或者default就会生效。
具体在哪使用的呢?
在Feign初始化阶段,从Spring容器读取FeignClientProperties
protected void configureFeign(FeignContext context, Feign.Builder builder) {
//查找`FeignClientProperties`
FeignClientProperties properties = this.applicationContext
.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
configureUsingProperties(properties.getConfig().get(this.contextId),
builder);
}
else {
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
configureUsingProperties(properties.getConfig().get(this.contextId),
builder);
configureUsingConfiguration(context, builder);
}
}
else {
configureUsingConfiguration(context, builder);
}
}
上面的代码就涉及到了配置优先级问题。默认是配置文件里的优先。
根据优先级确定配置后,构造Request.Options
对象,与FeignClient对象绑定。
protected void configureUsingProperties(
FeignClientProperties.FeignClientConfiguration config,
Feign.Builder builder) {
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Request.Options(config.getConnectTimeout(),
config.getReadTimeout()));
}
}
上面是初始化好了Feign的超时配置,如果使用了ribbon负载均衡,在执行阶段可能会被修改。
- 如果是OpenFeign默认配置,使用ribbon配置覆盖默认配置
org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#getClientConfig
IClientConfig getClientConfig(Request.Options options, String clientName) {
IClientConfig requestConfig;
//默认10s的配置情况,使用ribbon配置覆盖默认配置
if (options == DEFAULT_OPTIONS) {
requestConfig = this.clientFactory.getClientConfig(clientName);
}
else {
requestConfig = new FeignOptionsClientConfig(options);
}
return requestConfig;
}
执行请求前,如果有配置优先使用已有的配置,兜底使用ribbon配置。整体流程如下
@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
//存在配置了,读已经配置的,如果为空,使用ribbon配置
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
OpenFeign超时配置问题
配置都是生成FeignClient对象之前就设置好了,如果想动态实时生效是不支持的,修改超时时间需要重启服务。有没有办法不重启服务实时生效呢?
我们可以写一个aop切面,拦截feign.Client#execute
方法,第二个参数就是调用时候会使用的Options参数,只要修改第二个参数就可以了。
总结
1、OpenFeign支持配置类和配置文件配置超时时间,默认连接超时时间是10s,读数据时间6s,配置文件的优先生效
2、Ribbon支持配置超时时间,默认1s,如果OpenFeign没有单独配置超时时间,则会使用Ribbon的超时时间覆盖
3、OpenFeign超时时间是构造Client之前就初始化好了,不支持动态修改生效,可以通过aop拦截Client的execute方法修改。