前言:
在开发 Spring Cloud 微服务项目时候,Feign 调用是非常常见的,Feign 调用的底层还是 HTTP 的远程调用,会有超时问题,如果没有搞清楚超时问题,生产环境的调用肯那个会有种种问题出现,本篇我们来分享一下 Feign 调用的超时配置。
连接超时和读取超时配置
- ConnectTimeout(连接超时):Feign 是基于 HTTP 的远程调用,众所周知,HTTP 调用会进行 TCP 的三次握手,连接超时时间,就是多少秒没连接上,就会抛出超时异常,Feign 连接超时默认 10 秒。
- ReadTimeout(读取超时):HTTP 成功连接后,客户端发会送请求报文,服务端收到后解析并返回响应报文,在写出响应报文时,如果超过了设置的时间还没写完,就会抛出读取超时异常,在某些接口请求数据量大的时候,就很容易出现读取超时,Feign 读取超时默认 60 秒。
超时演示
我们在被 Feign 调用的接口中让线程 sleep 61 秒,调用者服务就抛出超时异常了。
Feign 默认超时时间源码
通过源码 debugger 我们可以知道 Feign 的默认读取超时时间是 60 秒,默认连接超时时间是 10 秒。
自定义 Feign 超时时间
Feign 支持在 ribbon 或者 feign 配置项下配置超时时间,feign 下配置优先级最高,但是新版 Spring Cloud 已经移除了 ribbon,因此建议配置在 feign中,如下配置:
#默认连接超时时间
feign.client.config.default.connect-timeout=5000
#默认读取超时时间
feign.client.config.default.read-timeout=3000
debugger 如下图,Feign 超时配置已经生效,default 表示作用于所有客户端,也可替换 default 为具体的客户端名称,表示作用于单个客户端,可以给每个客户端配置不同的超时时间。
给指定的服务端配置超时时间如下:
#默认连接超时时间
feign.client.config.order-service.connect-timeout=4000
#默认读取超时时间
feign.client.config.order-service.read-timeout=4000
debugger 验证如下:
Feign 源码分析
Feign 参数的初始化
Feign 通过接口生成代理对象,扫描到 Feign 接口构建代理对象,在 Feign#builder 创建构建者时,Feign 客户端相关的参数都是在这个时候初始化的,超时时间也是在这个时候初始化的,Feign#builder 会创建一个 Options 对象,源码如下:
//feign.Feign.Builder#Builder
public Builder() {
//日志级别
this.logLevel = Level.NONE;
this.contract = new Default();
//客户端
this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
//重试器
this.retryer = new feign.Retryer.Default();
//日志
this.logger = new NoOpLogger();
//编码器
this.encoder = new feign.codec.Encoder.Default();
//解码器
this.decoder = new feign.codec.Decoder.Default();
this.queryMapEncoder = new FieldQueryMapEncoder();
//错误解码器
this.errorDecoder = new feign.codec.ErrorDecoder.Default();
//超时配置
this.options = new Options();
//处理器工厂
this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
this.closeAfterDecode = true;
//传播策略
this.propagationPolicy = ExceptionPropagationPolicy.NONE;
//是否强制解码
this.forceDecoding = false;
this.capabilities = new ArrayList();
}
前面我们说 Feign 的默认连接超时时间是 10 秒,默认读取超时时间是 60 秒,我们来从源码证明,Options 构造方法源码如下:
//feign.Request.Options#Options()
public Options() {
this(10L, TimeUnit.SECONDS, 60L, TimeUnit.SECONDS, true);
}
//feign.Request.Options#Options(long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit, boolean)
public Options(long connectTimeout, TimeUnit connectTimeoutUnit, long readTimeout, TimeUnit readTimeoutUnit, boolean followRedirects) {
this.connectTimeout = connectTimeout;
this.connectTimeoutUnit = connectTimeoutUnit;
this.readTimeout = readTimeout;
this.readTimeoutUnit = readTimeoutUnit;
this.followRedirects = followRedirects;
}
从 Options 构造方法源码中可以证明 Feign 的默认超时时间。
SentinelFeign.Builder#build 方法源码分析
SentinelFeign.Builder#build 方法主要逻辑如下:
- 获取应用程序上下文。
- 通过应用程序上下文获取到 target 对象的 BeanDefinition 。
- 通过 BeanDefinition 获取到 FeignClientFactoryBean 。
- 获取 FallBack 类和工厂。
- 创建代理对象的处理器 SentinelInvocationHandler(项目中引入了 Sentinel)。
//com.alibaba.cloud.sentinel.feign.SentinelFeign.Builder#build
public Feign build() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
//target 就是我们定义的 feign 接口 例如 OrderFeign
//dispatch 就是我们的接口方法 例如 com.user.service.feign.OrderFeign.queryOrder()
//通用应用程序上下文
GenericApplicationContext gctx = (GenericApplicationContext)Builder.this.applicationContext;
//target 对象的 beanDefinition 对象
BeanDefinition def = gctx.getBeanDefinition(target.type().getName());
//FeignClient bean 工厂
FeignClientFactoryBean feignClientFactoryBean = (FeignClientFactoryBean)def.getAttribute("feignClientsRegistrarFactoryBean");
//Feign 接口中配置的的 fallback
Class fallback = feignClientFactoryBean.getFallback();
//fallback 工厂
Class fallbackFactory = feignClientFactoryBean.getFallbackFactory();