本文转载自微信公众号《程序员jinjunzhu》,作者jinjunzhu。转载本文请联系程序员jinjunzhu公众号。OpenFeign是SpringCloud中的一个重要组件,它是一个声明式的HTTP客户端。使用OpenFeign调用远程服务就像调用本地方法一样,但是如果使用不当,很容易踩坑。坑一:如果在HttpClient1.1feign中没有对http客户端进行特殊配置,OpenFeign默认使用jdk自带的HttpURLConnection。我们知道HttpURLConnection是没有连接池的,性能和效率都比较低。如果使用默认值,您可能会遇到导致系统故障的性能问题。可以使用ApacheHttpClient,在properties文件中添加如下配置:feign.httpclient.enabled=truepom在文件中添加依赖:io.github.openfeignfeign-httpclient9.3.1也可以使用OkHttpClient,在properties文件中添加如下配置:feign.okhttp.enabled=truepom文件添加依赖:io.github.openfeignfeign-okhttp10.2.01.2当ribbon中的HttpClient作为注册的客户端时通过OpenFeigncenter,默认使用Ribbon做负载均衡。Ribbon默认也是使用jdk自带的HttpURLConnection。需要给Ribbon设置一个Http客户端,比如使用okhttp,在properties文件中添加如下配置:ribbon.okhttp.enabled=true坑2:全局超时时间OpenFeign可以设置超时时间,简单粗暴,设置全局超时,如下:feign.client.config.default.connectTimeout=2000feign.client.config.default.readTimeout=60000如果不配置超时,默认是连接超时10s,读取超时60s,在源码定义在feign.Request的内部类Options中。该接口设置了最大readTimeout为60s,必须大于调用的所有外部接口的readTimeout,否则处理时间大于readTimeout的接口将调用失败。如下图所示,在一个系统中使用OpenFeign调用三个外部服务,每个服务提供两个接口。serviceC的一个接口需要60秒返回,所以上面的readTimeout必须设置为60秒。但是如果serviceA失败了,就意味着接口1只能在60秒后返回,所以OpenFeign只能等到读取超时。如果调用该接口的并发度很高,会占用大量的连接资源,直到资源耗尽,系统崩溃。为防止此类故障,必须确保接口1可以快速故障。最好的办法是为serviceC单独设置超时时间。坑3:单个服务设置超时时间从上一节的解释可以看出,serviceC需要单独设置一个超时时间。代码如下:feign.client.config.serviceC.connectTimeout=2000feign.client.config.serviceC.readTimeout=60000这个时间会覆盖第一节的默认超时时间。但是问题又来了,serviceD从serviceC上掉线了,因为serviceD的故障导致了接口6的读取超时,为了防止系统崩溃,只好对serviceC的接口5单独设置超时时间。如下图:坑4:熔断超时如何设置单个接口的超时时间,查了网上的资料,一定要开启熔断,配置如下:feign.hystrix.enabled=true熔断后启用后,您可以配置单个接口的超时时间。如果接口5调用serviceC的声明如下:@FeignClient(value="serviceC"configuration=FeignMultipartSupportConfig.class)publicinterfaceServiceCClient{@GetMapping("/interface5")Stringinterface5(Stringparam);}根据interface5接口的声明以上,在properties文件中加入如下配置:hystrix.command.ServiceCClient#interface5(param).execution.isolation.thread.timeoutInMilliseconds=60000网上的信息不准确,这个timeout不起作用。为什么不生效?4.1使用feigntimeout最终使用的超时时间来自Options类。如果我们配置feigntimeout,我们会选择使用feigntimeout。以下代码在FeignClientFactoryBean类的configureUsingProperties方法中:)));}4.2使用ribbontimeout如果没有配置feign,但是配置了ribbontimeout,就会使用ribbontimeout。我们来看看这段源码,FeignLoadBalancer中的execute方法,publicRibbonResponseexecute(RibbonRequestrequest,IClientConfigconfigOverride)throwsIOException{Request.Optionsoptions;if(configOverride!=null){RibbonPropertiesoverride=RibbonProperties.from(configOverrides);options=new.connectTimeout(this.connectTimeout),override.readTimeout(this.readTimeout));}else{options=newRequest.Options(this.connectTimeout,this.readTimeout);}//本次请求中的客户端为OkHttpClientResponseresponse=request.client()。执行(request.toRequest(),选项);returnnewRibbonResponse(request.getUri(),response);}4.3如何使用自定义Options配置单个接口的超时时间,这里给出解决方案,如果大家有其他方案欢迎讨论。我的方案是使用RestTemplate来调这个接口,单独配置超时时间,配置代码如下,这里使用OkHttpClient:publicclassRestTemplateConfiguration{@BeanpublicOkHttp3ClientHttpRequestFactoryokHttp3RequestFactory(){OkHttp3ClientHttpRequestFactoryrequestFactory=newOkHttp3ClientHttpRequestFactory();requestFactory.setConnectTimeout(2000);requestFactory.setReadTimeout(60000);returnrequestFactory;}@Bean@LoadBalancedpublicRestTemplaterestTemplate(OkHttp3ClientHttpRequestFactoryokHttp3RequestFactory){returnnewRestTemplate(okHttp3RequestFactory);}}为了使用ribbon负载均衡,上面加了@LoadBalanced如果使用RestTemplate,就会使用OkHttp3ClientHttpRequestFactory中配置的时间。Pit5:Ribbontimeoutisusedasloadbalancing,ribbontimeoutisalsoconfigurable,youcanaddthefollowingconfigurationinproperties:ribbon.ConnectTimeout=2000ribbon.ReadTimeout=11000Thereisanarticlethatthetimeouttimeofribbonconfigurationmustmeettheinterfaceresponsetime,infactOtherwise,itisenoughtoconfigurefeign'stimeout,becauseitcanoverridetheribbon'stimeout.Pit6:RetryisnotenabledbydefaultOpenFeigndoesnotsupportretrybydefault,whichcanbeseeninfeignRetryerinthesourcecodeFeignClientsConfiguration.@Bean@ConditionalOnMissingBeanpublicRetryerfeignRetryer(){returnRetryer.NEVER_RETRY;}要开启重试,我们可以自定义Retryer,比如下面这行代码:Retryerretryer=newRetryer.Default(100,1000,2);表示每次间隔100ms,最大间隔1000ms重试一次,最大重试次数为1,因为第三个参数包含了第一次请求。坑七:Ribbon重试7.1拉取服务列表Ribbon默认从服务器拉取列表的时间间隔是30s,这个对优雅发布不友好,一般我们会缩短这个时间,改成3s如下:serviceC.ribbon.ServerListRefreshInterval=37.2RetryRibbon重试Ribbon时需要注意的地方有很多。这是其中的4个。1、同一个实例的最大重试次数,不包括第一次调用,配置如下:serviceC.ribbon.MaxAutoRetries=1这个次数不包括第一次调用。如果配置为1,则重试策略将首先尝试在失败的实例上重试一次。如果失败,请请求下一个实例。2、同一服务的其他实例的最大重试次数,不包括第一次调用的实例。默认值为1:serviceC.ribbon.MaxAutoRetriesNextServer=13。是否重试所有操作,如果改为true,则重试所有操作请求,包括post,建议使用默认配置false。serviceC.ribbon.OkToRetryOnAllOperations=false4.重试指定的http状态码serviceC.retryableStatusCodes=404,408,502,500坑8:hystrix超时如下图:默认不开启hystrix,但是如果开启hystrix,因为hystrix在Ribbon外,会超时需要满足以下规则:hystrixtimeout>=(MaxAutoRetries+1)*(ribbonConnectTimeout+ribbonReadTimeout)如果Ribbon不重试,MaxAutoRetries=0根据上面的公式,如果我们配置熔断超时时间如下:hystrix.command.ServiceCClient#interface5(param).execution.isolation.thread.timeoutInMilliseconds=15000ribbon.ReadTimeout=8000这个配置不会重试一次。当serviceA调用serviceB时,hystrix会等待Ribbon返回的结果。如果Ribbon配置为重试,hystrix会一直等到超时。对于上面的配置,因为第一次请求已经用了8s,剩下的7s不够请求一次,所以不会重试。坑9:即使不需要注册中心,使用OpenFeign做http客户端也很方便,但是需要注意三点:当不需要配置ribbon相关参数,使用RestTemplate调用时,你在使用OpenFeign的过程中不考虑负载均衡来组装自己的一组请求,与直接使用http客户端相比,会有一定的开销。使用OpenFeign有很多配置坑。对于没有注册中心的情况,建议直接使用http客户端。