OpenFeign 的 9 个坑,每个都能让你的系统奔溃 - 今日头条

本文由 简悦 SimpRead 转码, 原文地址 www.toutiao.com

如果不做特殊配置,OpenFeign 默认使用 jdk 自带的 HttpURLConnection,我们知道 HttpURLConnection 没有连接池

作者 | jinjunzhu

来源 | jinjunzhu

OpenFeign 是 SpringCloud 中的重要组件,它是一种声明式的 HTTP 客户端。使用 OpenFeign 调用远程服务就像调用本地方法一样,但是如果使用不当,很容易踩到坑。

https://p6.toutiaoimg.com/origin/pgc-image/SNITKRIBqnnhoh?from=pc

feign 中 http client

如果不做特殊配置,OpenFeign 默认使用 jdk 自带的 HttpURLConnection,我们知道 HttpURLConnection 没有连接池、性能和效率比较低,如果采用默认,很可能会遇到性能问题导致系统故障。

可以采用 Apache HttpClient,properties 文件中增加下面配置:

1
feign.httpclient.enabled=true
1
feign.httpclient.enabled=true

也可以采用 OkHttpClient,properties 文件中增加下面配置:

1
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> <version>9.3.1</version></dependency>

pom 文件中增加依赖:

1
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> <version>9.3.1</version></dependency>

通过 OpenFeign 作为注册中心的客户端时,默认使用 Ribbon 做负载均衡,Ribbon 默认也是用 jdk 自带的 HttpURLConnection,需要给 Ribbon 也设置一个 Http client,比如使用 okhttp,在 properties 文件中增加下面配置:

1
feign.okhttp.enabled=true

https://p6.toutiaoimg.com/origin/pgc-image/SdjMPuI9HbkPe1?from=pc

坑二:全局超时时间

OpenFeign 可以设置超时时间,简单粗暴,设置一个全局的超时时间,如下:

1
feign.okhttp.enabled=true

如果不配置超时时间,默认是连接超时 10s,读超时 60s,在源码 feign.Request 的内部类 Options 中定义。

这个接口设置了最大的 readTimeout 是 60s,这个时间必须大于调用的所有外部接口的 readTimeout,否则处理时间大于 readTimeout 的接口就会调用失败。

如下图,在一个系统中使用 OpenFeign 调用外部三个服务,每个服务提供两个接口,其中 serviceC 的一个接口需要 60 才能返回,那上面的 readTimeout 必须设置成 60s。

https://p6.toutiaoimg.com/origin/pgc-image/SeYhXRk1EQfXae?from=pc

但是如果 serviceA 出故障了,表现是接口 1 超过 60s 才能返回,这样 OpenFeign 只能等到读超时,如果调用这个接口的并发量很高,会大量占用连接资源直到资源耗尽系统奔溃。要防止这样的故障发生,就必须保证接口 1 能 fail-fast。最好的做法就是给 serviceC 单独设置超时时间。

https://p6.toutiaoimg.com/origin/pgc-image/SdjMPug8ywO11K?from=pc

坑三:单服务设置超时时间

从上一节的讲解我们看到,需要对 serviceC 单独设置一个超时时间,代码如下:

1
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>10.2.0</version></dependency>ribbon中的Http Client

这个时间会覆盖第一节中默认的超时时间。但是问题又来了,serviceC 中又掉了 serviceD,因为 serviceD 的故障导致接口 6 发生了读超时的情况,为了不让系统奔溃,不得不对 serviceC 的接口 5 单独设置超时时间。如下图:

https://p6.toutiaoimg.com/origin/pgc-image/SeYhXSMOOafae?from=pchttps://p6.toutiaoimg.com/origin/pgc-image/SdjMPvH8ZnEHyf?from=pc

坑四:熔断超时时间

怎样给单个接口设置超时时间,查看网上资料,必须开启熔断,配置如下:

1
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>10.2.0</version></dependency>

开启熔断后,就可以给单个接口配置超时了。如果调用 serviceC 的接口 5 的声明如下:

1
ribbon.okhttp.enabled=true

根据上面 interface5 接口的声明,在 properties 文件中增加如下配置:

1
ribbon.okhttp.enabled=true

网上资料说的并不准确,这个超时时间并没有起作用。为什么不生效呢?

使用 feign 超时

最终使用的超时时间来自于 Options 类。如果我们配置了 feign 的超时时间,会选择使用 feign 超时时间,下面代码在 FeignClientFactoryBean 类的 configureUsingProperties 方法:

1
feign.client.config.default.connectTimeout=2000feign.client.config.default.readTimeout=60000

使用 ribbon 超时

如果没有配置 feign,但是配置了 ribbon 的超时时间,会使用 ribbon 的超时时间。我们看下这段源代码,FeignLoadBalancer 里面的 execute 方法,

1
feign.client.config.default.connectTimeout=2000feign.client.config.default.readTimeout=60000

使用自定义 Options

对于单个接口怎么配置超时时间,我这里给出一个方案,如果你有其他方案,欢迎探讨。我的方案是使用 RestTemplate 来调这个接口,单独配置超时时间,配置代码如下,这里使用 OkHttpClient:

1
feign.client.config.serviceC.connectTimeout=2000feign.client.config.serviceC.readTimeout=60000

为了使用 ribbon 负载均衡,上面加了 @LoadBalanced

如果使用 RestTemplate,就会使用 OkHttp3ClientHttpRequestFactory 中配置的时间。

https://p6.toutiaoimg.com/origin/pgc-image/SLyLiDbAhsVBHs?from=pc

坑五:ribbon 超时时间

作为负载均衡,ribbon 超时时间也是可以配置的,可以在 properties 增加下面配置:

1
feign.client.config.serviceC.connectTimeout=2000feign.client.config.serviceC.readTimeout=60000

有文章讲 ribbon 配置的超时时间必须要满足接口响应时间,其实不然,配置 feign 的超时时间就足够了,因为它可以覆盖掉 ribbon 的超时时间。

https://p6.toutiaoimg.com/origin/pgc-image/SMAi16zAwOzjvj?from=pc

坑六:重试默认不开启

OpenFeign 默认是不支持重试的,可以在源代码 FeignClientsConfiguration 中 feignRetryer 中看出。

1
feign.hystrix.enabled=true

要开启重试,我们可以自定义 Retryer,比如下面这行代码:

1
feign.hystrix.enabled=true

表示每间隔 100ms,最大间隔 1000ms 重试一次,最大重试次数是 1,因为第三个参数包含了第一次请求。

https://p6.toutiaoimg.com/origin/pgc-image/SMAi20M4sjhK5e?from=pc

拉取服务列表

Ribbon 默认从服务端拉取列表的时间间隔是 30s,这个对优雅发布很不友好,一般我们会把这个时间改短,如下改成 3s:

1
@FeignClient(value = "serviceC"configuration = FeignMultipartSupportConfig.class)public interface ServiceCClient { @GetMapping("/interface5") String interface5(String param);}

重试

Ribbon 重试有不少需要注意的地方,这里分享 4 个。

  1. 同一实例最大重试次数,不包括首次调用,配置如下:
1
@FeignClient(value = "serviceC"configuration = FeignMultipartSupportConfig.class)public interface ServiceCClient { @GetMapping("/interface5") String interface5(String param);}

这个次数不包括首次调用,配置了 1,重试策略会先尝试在失败的实例上重试一次,如果失败,请求下一个实例。

  1. 同一个服务其他实例的最大重试次数,这里不包括第一次调用的实例。默认值为 1:
1
hystrix.command.ServiceCClient#interface5(param).execution.isolation.thread.timeoutInMilliseconds=60000
  1. 是否对所有操作都重试,如果改为 true,则对所有操作请求都进行重试, 包括 post,建议采用默认配置 false。
1
if (config.getConnectTimeout !=  && config.getReadTimeout != ) { builder.options(new Request.Options(config.getConnectTimeout, config.getReadTimeout));}
  1. 对指定的 http 状态码进行重试
1
if (config.getConnectTimeout !=  && config.getReadTimeout != ) { builder.options(new Request.Options(config.getConnectTimeout, config.getReadTimeout));}

https://p6.toutiaoimg.com/origin/pgc-image/SMYsVoeGAx44R9?from=pc

如下图:

https://p6.toutiaoimg.com/origin/pgc-image/SeYhXij3oR8bWC?from=pc

hystrix 默认不开启,但是如果开启了 hystrix,因为 hystrix 是在 Ribbon 外面,所以超时时间需要符合下面规则:hystrix 超时 >= (MaxAutoRetries + 1) * (ribbon ConnectTimeout + ribbon ReadTimeout)

如果 Ribbon 不重试,MaxAutoRetries=0

根据上面公式,假如我们配置熔断超时时间如下:

1
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException { Request.Options options; if (configOverride != ) { 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); } //这个request里面的client就是OkHttpClient Response response = request.client.execute(request.toRequest, options); return new RibbonResponse(request.getUri, response);}

这个配置是不会重试一次的。serviceA 调用 serviceB 时,hystrix 会等待 Ribbon 返回的结果,如果 Ribbon 配置了重试,hystrix 会一直等待直到超时。上面的配置,因为第一次请求已经耗去了 8s,剩下时间 7s 不够请求一次了,所以是不会进行重试的。

即使不用注册中心,使用 OpenFeign 做普通 http 客户端也是很方便的,但是有三点需要注意:

  • 不用配置 ribbon 相关参数
  • 使用 RestTemplate 调用时,不考虑负载均衡
  • 使用过程中 OpenFeign 要组装出自己的一套请求,跟直接使用 http 客户端比,会有一定开销

使用 OpenFeign 有很多配置上的坑,对于没有注册中心的情况,建议直接使用 http 客户端

https://p6.toutiaoimg.com/origin/pgc-image/SNINFms5qTXSbB?from=pc