今天來(lái)聊一聊前段時(shí)間看到的一個(gè)面試題,也是在實(shí)際項(xiàng)目中需要考慮的一個(gè)問(wèn)題,F(xiàn)eign 的超時(shí)時(shí)間如何設(shè)置?
Feign 的超時(shí)時(shí)間設(shè)置方式并不固定,它取決于 Feign 在項(xiàng)目中是如何使用的,不同的使用方式,超時(shí)時(shí)間設(shè)置方式也不大相同,甚至還可能有坑。
前置知識(shí)
由于文章會(huì)涉及到 Feign 的底層知識(shí),如果不懂點(diǎn) Feign 的基本概念的話,后面就看不下去了。
所以為了方便不了解 Feign 的小伙伴也能夠讀得懂文章,這里我就簡(jiǎn)單地說(shuō)說(shuō) Feign 的原理,點(diǎn)到為止,雖然不深入,但足夠應(yīng)付這篇文章了。
Feign 的作用
在項(xiàng)目中,我們經(jīng)常需要調(diào)用第三方提供的 Http 接口,此時(shí)我們就可以使用一些 Http 框架來(lái)實(shí)現(xiàn),比如 HttpClient。
publicclassHttpClientDemo{ publicstaticvoidmain(String[]args)throwsException{ //創(chuàng)建一個(gè)HttpClient HttpClienthttpClient=HttpClientBuilder.create().build(); //構(gòu)建一個(gè)get請(qǐng)求 HttpGethttpGet=newHttpGet("http://192.168.100.1:8080/order/1"); //發(fā)送請(qǐng)求,獲取響應(yīng) HttpResponsehttpResponse=httpClient.execute(httpGet); HttpEntityhttpEntity=httpResponse.getEntity(); //讀出響應(yīng)值 Stringresponse=EntityUtils.toString(httpEntity); System.out.println("Response:"+response); } }
如果項(xiàng)目中只有一兩個(gè)這種第三方接口這樣寫(xiě)還行,但是一旦這種三方接口過(guò)多的話,每次都得這樣組裝參數(shù),發(fā)送請(qǐng)求,寫(xiě)一堆同樣的代碼,就顯然很麻煩了。
所以為了簡(jiǎn)化發(fā)送 Http 請(qǐng)求的開(kāi)發(fā),減少重復(fù)代碼,F(xiàn)eign 就出現(xiàn)了。
Feign 是一個(gè)聲明式的 Http 框架
當(dāng)你需要調(diào)用 Http 接口時(shí),你需要聲明一個(gè)接口,加一些注解就可以了。而像組裝參數(shù)、發(fā)送 Http 請(qǐng)求等重復(fù)性的工作都交給 Feign 來(lái)完成。
Feign 的原理
雖然有了接口,但是僅僅有接口是不夠的,因?yàn)榻涌谟植荒軇?chuàng)建對(duì)象,我們得需要對(duì)象。
Feign 為了方便我們?yōu)榻涌趧?chuàng)建對(duì)象,提供的 Feign.Builder 這個(gè)內(nèi)部類。
這個(gè)類的作用就是解析接口的上的注解,為接口生成一個(gè)動(dòng)態(tài)代理對(duì)象,后面通過(guò)這個(gè)代理對(duì)象就可以發(fā)送請(qǐng)求了。
這個(gè)內(nèi)部類有很多屬性,這些屬性都是 Feign 的核心組件。
在這些核心的組件中有一個(gè)叫 Client 的,上圖中我圈出來(lái)了。
這個(gè) Client 類劃個(gè)重點(diǎn),非常非常重要,本文討論的東西跟他有密切關(guān)系。
它只有一個(gè)方法 Response execute (Request request, Options options)
方法的第一個(gè)參數(shù) Request 就是封裝了 http 請(qǐng)求的 url、請(qǐng)求方法,請(qǐng)求頭、請(qǐng)求體之類的參數(shù)。
第二個(gè)參數(shù) Options 就是本文的主題,封裝了超時(shí)時(shí)間。
返回值 Response就是封裝了一些響應(yīng)碼 status、響應(yīng)頭之類的。
所以通過(guò)方法的參數(shù)和返回值也可以猜出來(lái),這個(gè) Client 作用是用來(lái)組裝 HTTP 請(qǐng)求參數(shù),發(fā)送 HTTP 請(qǐng)求的。并且 HTTP 請(qǐng)求超時(shí)時(shí)間是根據(jù)傳給 Client 的 Options 參數(shù)來(lái)決定的。
Feign 單獨(dú)使用時(shí)超時(shí)時(shí)間設(shè)置
Feign 本身就是一個(gè) HTTP 客戶端,可獨(dú)立使用,F(xiàn)eign 提供了兩種超時(shí)時(shí)間設(shè)置方式。
1、通過(guò) Feign.Builder 設(shè)置
前面提到,F(xiàn)eign.Builder 的作用是為接口的動(dòng)態(tài)代理對(duì)象的。
Feign.Builder 里面有很多屬性,其中就有關(guān)于超時(shí)時(shí)間的屬性 Options。
如果你不設(shè)置,那么超時(shí)時(shí)間就是默認(rèn)的。
默認(rèn)的就是連接超時(shí) 10s,讀超時(shí) 60s。
所以可以通過(guò)設(shè)置 Feign.Builder 中的 options 來(lái)設(shè)置超時(shí)時(shí)間。
來(lái)個(gè) demo
環(huán)境準(zhǔn)備,就是一個(gè)簡(jiǎn)單的 SpringBoot 項(xiàng)目,引入一個(gè) Feign 的依賴。
這里演示的是 Feign 原生的使用方式,脫離于 SpringCloud 環(huán)境,所以 Spring 的那些 @GetMappring 就不支持了,改用 Feign 本身提供的注解。
測(cè)試代碼
這里面的請(qǐng)求路徑都是不存在的,因?yàn)槲覀冎魂P(guān)心傳給 Client 的 Options 參數(shù)值。
Client 在我們不設(shè)置的時(shí)候,就用默認(rèn)的實(shí)現(xiàn) Client.Default。
斷點(diǎn)打到 execute 方法的實(shí)現(xiàn),運(yùn)行,走起。
結(jié)果就是我們?cè)O(shè)置的 5s。
2、在接口方法參數(shù)設(shè)置
除了在通過(guò) Feign.Builder 時(shí)設(shè)置之外,F(xiàn)eign 還支持在接口的方法參數(shù)上設(shè)置。
此時(shí)你只需要在接口的方法上加一個(gè) Options 類型的參數(shù)。
@RequestLine("GET/user/{userId}") UserqueryUser(@Param("userId")IntegeruserId,Request.Optionsoptions);
這樣在傳參數(shù)時(shí)就可以設(shè)置超時(shí)時(shí)間了。
Useruser=client.queryUser(123,newRequest.Options(3,TimeUnit.SECONDS,3,TimeUnit.SECONDS,true));
同樣地,debug 就可以看見(jiàn)我們?cè)O(shè)置的 3s 了。
這兩種設(shè)置超時(shí)時(shí)間的主要區(qū)別就是方法參數(shù)設(shè)置超時(shí)時(shí)間的優(yōu)先級(jí)高于 Feign.Builder 設(shè)置的超時(shí)時(shí)間。
用一張圖來(lái)總結(jié)一下上面的關(guān)系。
所以,如果你單獨(dú)使用 Feign 的時(shí)候,你就可以通過(guò)如上的兩種方式來(lái)設(shè)置超時(shí)時(shí)間。
SpringCloud 下 Feign 單獨(dú)使用超時(shí)時(shí)間設(shè)置
在 SpringCloud 環(huán)境下,只是對(duì) Feign 進(jìn)行了一層包裝,所以即使沒(méi)有 Ribbon 和注冊(cè)中心,F(xiàn)eign 也是可以單獨(dú)使用的,但是用法有點(diǎn)變化
注解都換成 SpringMVC 的注解
接口上需要加 @FeignClient 注解
用 @EnableFeignClients 掃描這些接口
不過(guò),默認(rèn)情況下 Feign 還是需要結(jié)合 Ribbon 來(lái)使用的。
如果你只想單獨(dú)使用 Feign,那么就設(shè)置一下 @FeignClient 注解的 url 屬性,指定請(qǐng)求的地址和端口就可以了。
所以,既然只是包裝,前面提到的兩種方式設(shè)置超時(shí)時(shí)間當(dāng)然可以繼續(xù)使用:
通過(guò) Feign.Builder
通過(guò)接口的方法參數(shù)
方法參數(shù)設(shè)置形式跟前面提到的一模一樣,但是通過(guò) Feign.Builder 來(lái)設(shè)置卻不太一樣。
由于 SpringCloud 會(huì)自己創(chuàng)建 Feign.Builder,不需要我們創(chuàng)建,所以在設(shè)置 Options 時(shí),Spring 提供了兩種快捷方式來(lái)設(shè)置。不過(guò)最終還是設(shè)置到 Feign.Builder 中。
1、聲明一個(gè) Options Bean
Spring 在構(gòu)建 Feign.Builder 的時(shí),會(huì)從容器中查找 Options 這個(gè) Bean,然后設(shè)置到 Feign.Builder 中。
@Configuration publicclassFeignConfiguration{ @Bean publicRequest.Optionsoptions(){ returnnewRequest.Options(8,TimeUnit.SECONDS,8,TimeUnit.SECONDS,true); } }
此時(shí) debug 就可以看到設(shè)置到 Feign.Builder 的代碼。
這段代碼在 FeignClientFactoryBean 中的 configureUsingConfiguration 方法中.
2、配置文件中設(shè)置
除了聲明 Bean 之外,Spring 還提供了通過(guò)配置文件的方式配置,如下:
同樣地,debug 就可以看見(jiàn)。
這段代碼在 FeignClientFactoryBean 中的 configureUsingConfiguration 方法中。
2、配置文件中設(shè)置
除了聲明 Bean 之外,Spring 還提供了通過(guò)配置文件的方式配置,如下:
feign: client: config: default: connectTimeout:10000 readTimeout:10000
同樣地,debug 就可以看見(jiàn)。
這段代碼在 FeignClientFactoryBean 中的 configureUsingConfiguration 方法中。
聲明 Bean 和配置文件都可以設(shè)置,那么同時(shí)設(shè)置哪種優(yōu)先級(jí)高呢?
如無(wú)特殊配置,遵守 SpringBoot 本身的配置規(guī)定
約定 > 配置 > 編碼
所以基于這個(gè)規(guī)定,配置文件的配置優(yōu)先級(jí)大于手動(dòng)聲明 Bean 的優(yōu)先級(jí)。
到這,我們又學(xué)到了兩種 Spring 為了方便我們?cè)O(shè)置 Feign.Builder 提供的配置方式:
聲明 Options Bean
配置文件
把他們倆加到前面畫(huà)的圖中:
所以,如果你使用了 SpringCloud 提供的方式來(lái)使用 Feign,那么就可以通過(guò)聲明 OptionsBean 和配置文件的方式更加方便地來(lái)設(shè)置超時(shí)時(shí)間。
最終其實(shí)還是通過(guò) Feign.Builder 來(lái)設(shè)置的。
SpringCloud 下通過(guò) Ribbon 來(lái)設(shè)置
當(dāng) Feign 配合 Ribbon 使用時(shí),除了上面兩種方式之外,還可以通過(guò) Ribbon 來(lái)設(shè)置超時(shí)時(shí)間。
但是這里我不知道你會(huì)不會(huì)好奇:
Ribbon 不是負(fù)載均衡組件,怎么可以設(shè)置超時(shí)時(shí)間?
跟 Ribbon 的定位有關(guān),除了負(fù)載均衡組件之外,Ribbon 也干發(fā)送 HTTP 請(qǐng)求的事,也就是不配合 Feign,他照樣可以發(fā)送 HTTP 請(qǐng)求。
來(lái)個(gè)簡(jiǎn)單 demo
解釋一下上面的代碼意思:
第一步,設(shè)置 user 服務(wù)的兩個(gè)服務(wù)實(shí)例地址;
第二步,獲取 user 服務(wù)對(duì)應(yīng)的 RestClient,這 RestClient 就可以用來(lái)發(fā)送 HTTP 請(qǐng)求;
第三步,構(gòu)建一個(gè) HTTP 請(qǐng)求;
第四步,就是發(fā)送 HTTP 請(qǐng)求,以負(fù)載均衡的方式。
這樣,此時(shí)就會(huì)從兩個(gè)服務(wù)實(shí)例中根據(jù)負(fù)載均衡選取一個(gè)服務(wù)地址發(fā)送 HTTP 請(qǐng)求,Ribbon 既然可以發(fā)送 HTTP 請(qǐng)求,那么自然而然就可以設(shè)置超時(shí)時(shí)間
Feign 在整合 Ribbon 的時(shí)候,為了統(tǒng)一配置,就默認(rèn)將自己的超時(shí)時(shí)間交由 Ribbon 管理
所以,在默認(rèn)情況下,F(xiàn)eign 的超時(shí)時(shí)間可以由 Ribbon 配置。而 Ribbon 默認(rèn)連接和讀超時(shí)時(shí)間只有 1s,所以在默認(rèn)情況下,F(xiàn)eign 的超時(shí)時(shí)間只有 1s。
IClientConfig 是 Ribbon 的配置類,Ribbon 所有的配置都可以從 IClientConfig 中獲取。
所以,在默認(rèn)情況下,很容易就發(fā)生超時(shí),不過(guò)我們可以通過(guò)配置文件修改即可。
ribbon: ConnectTimeout:5000 ReadTimeout:5000
你知道你發(fā)現(xiàn)沒(méi),上面說(shuō)通過(guò) Ribbon 設(shè)置 Feign 的超時(shí)時(shí)間,一直提到前面一直提到這個(gè)詞“默認(rèn)”。
什么情況下叫默認(rèn)呢?
所謂的默認(rèn),就是當(dāng)你不主動(dòng)設(shè)置 Feign 的超時(shí)時(shí)間的時(shí)候,就是默認(rèn)。
換句話說(shuō),一旦你通過(guò)上面說(shuō)的那些配置方式設(shè)置 Feign 的超時(shí)時(shí)間,就不是默認(rèn)了
此時(shí)通過(guò) Ribbon 設(shè)置的超時(shí)時(shí)間就不會(huì)生效了。
Feign 是如何在默認(rèn)情況下將超時(shí)時(shí)間交給 Ribbon 管理的?
要想回答這個(gè)問(wèn)題,就得先搬出前面反復(fù)提到的 Client 接口了。
在 SpringCloud 的環(huán)境下,有一個(gè) Client 的實(shí)現(xiàn),叫 LoadBalancerFeignClient。
通過(guò)名字就可以看出,帶有負(fù)載均衡的 Client 實(shí)現(xiàn),負(fù)載均衡的實(shí)現(xiàn)肯定是交給 Ribbon 來(lái)實(shí)現(xiàn)的。
所以,當(dāng) Feign 配合 Ribbon 時(shí)用的就是這個(gè) Client 實(shí)現(xiàn)。
既然實(shí)現(xiàn)了 Client 接口,那就看看 execute 方法的實(shí)現(xiàn)邏輯。
圖中 getClientConfig 方法就是判斷使用 Feign 或者 Ribbon 配置的核心邏輯。
核心的判斷邏輯就是這一行:
options==DEFAULT_OPTIONS
DEFAULT_OPTIONS 就是一個(gè)超時(shí)時(shí)間的常量。
當(dāng)上述判斷條件成立時(shí),就會(huì)通過(guò) this.clientFactory.getClientConfig(clientName) (clientName) 獲取到 Ribbon 配置。
由于這是 Ribbon 的邏輯,這里就不深扒了,知道是這個(gè)意思就行。
當(dāng)條件不成立時(shí),用 Options 構(gòu)建一個(gè) FeignOptionsClientConfig。
FeignOptionsClientConfig 就是簡(jiǎn)單地將 Options 配置讀出來(lái),設(shè)置到父類 DefaultClientConfigImpl 超時(shí)時(shí)間配置上。
DefaultClientConfigImpl 就算你不知道是什么也無(wú)所謂,你能看出的一件事就是,超時(shí)時(shí)間用的是傳遞給 Client 的 Options 參數(shù)。
所以,綜上,我們的問(wèn)題就變得非常 easy 了,那就是什么時(shí)候。
options==DEFAULT_OPTIONS
只有當(dāng)這個(gè)條件成立時(shí),才使用 Ribbon 的配置。
這里我們先來(lái)捋一捋前面提到的東西。
前面我們反復(fù)提到,Client 的 Options 最終只來(lái)自于兩種配置:
Feign.Builder
方法參數(shù)
所以 DEFAULT_OPTIONS 這個(gè) Options 一定是通過(guò)上面兩種方法中的其中一種設(shè)置的。
而方法參數(shù)是不可能設(shè)置的成 DEFAULT_OPTIONS。
因?yàn)檫@是我們控制的,只要我們參數(shù)不傳 DEFAULT_OPTIONS,那么永遠(yuǎn)都不可能是 DEFAULT_OPTIONS。
此時(shí)只剩下一種情況,那就是 Spring 在構(gòu)建在 Feign.Builder 的時(shí)候,設(shè)置成 DEFAULT_OPTIONS。
通過(guò)查找 DEFAULT_OPTIONS 的使用,我們可以追蹤到這么一段代碼。
這不就是前面提到的通過(guò)聲明 Bean 的方式來(lái)設(shè)置超時(shí)時(shí)間。
不同的是它加了 @ConditionalOnMissingBean,這個(gè)注解就是說(shuō),一旦我們自己沒(méi)有聲明 Options,就用他這個(gè) Options。
到這終于真像大白了。
我們不設(shè)置超時(shí)時(shí)間,Spring 就會(huì)給 Feign.Builder 加一個(gè) DEFAULT_OPTIONS 這個(gè) Options。
在執(zhí)行的時(shí)候,發(fā)現(xiàn)是 DEFAULT_OPTIONS,說(shuō)明我們沒(méi)有主動(dòng)設(shè)置過(guò)超是時(shí)間,就會(huì)使用 Ribbon 的超時(shí)時(shí)間。
為了方便理清上面的邏輯,這里整一張圖。
雖然 Feign 可以使用 Ribbon 的超時(shí)時(shí)間,但是 Ribbon 的配置的優(yōu)先級(jí)是最最低的。
方法參數(shù) > Feign 配置文件 > 聲明 Options > Ribbon 配置
Feign or Ribbon 配置用哪個(gè)好?
其實(shí)我個(gè)人更傾向于使用 Ribbon 的配置方式。
因?yàn)?Ribbon 除了可以設(shè)置超時(shí)時(shí)間之外,還可以配置重試機(jī)制、負(fù)載均衡等其它的配置
為了簡(jiǎn)化和統(tǒng)一管理配置,使用 Ribbon 來(lái)配置超時(shí)時(shí)間。
可能你會(huì)有疑問(wèn),F(xiàn)eign 也支持重試機(jī)制,為什么不選擇 Feign?
這是因?yàn)?Feign 重試機(jī)制沒(méi)有 Ribbon 的好。
Ribbon 重試的時(shí)候會(huì)換一個(gè)服務(wù)實(shí)例來(lái)重試,因?yàn)樵瓉?lái)出錯(cuò)的可能不可用。
而 Feign 并不會(huì)換一個(gè)服務(wù)實(shí)例重試,他并不知道上一次使用的是哪個(gè)服務(wù)實(shí)例,這就導(dǎo)致可能會(huì)出現(xiàn)在一個(gè)不可用的服務(wù)實(shí)例上多次重試的情況。
引入 Hystrix 時(shí)超時(shí)時(shí)間設(shè)置
如果你之前的確沒(méi)有研究過(guò)關(guān)于 Feign 超時(shí)時(shí)間的配置關(guān)系,那么此時(shí)你應(yīng)該有所收獲了。
但是這就結(jié)束了么?
不,事情沒(méi)那么簡(jiǎn)單。
如果你的項(xiàng)目中使用了 Hystrix,那么就得小心前面說(shuō)的那些配置了。
由于 Hystrix 跟 Feign 畢竟是一家人,所以當(dāng)引入 Hystrix 時(shí),F(xiàn)eign 就跟之前不一樣了。
Hystrix 會(huì)去干一件事,那就是給每個(gè) Feign 的 HTTP 接口保護(hù)起來(lái),畢竟 Hystrix 就是干保鏢這個(gè)事的。
但是這沒(méi)保護(hù)還好,一保護(hù)問(wèn)題就不自覺(jué)地出現(xiàn)了。
Hystrix 在保護(hù)的時(shí)候,一旦發(fā)現(xiàn)被保護(hù)的接口執(zhí)行的時(shí)間超過(guò) Hystrix 設(shè)置的最大時(shí)間,就直接進(jìn)行降級(jí)操作。
怎么降級(jí)的,這里咱不關(guān)心,咱關(guān)心的是這個(gè) Hystrix 超時(shí)的最大值是多少。
因?yàn)橐坏┻@個(gè)時(shí)間小于 Feign 的超時(shí)時(shí)間,那么就會(huì)出現(xiàn) Http 接口正在執(zhí)行,也沒(méi)有異常,僅僅是因?yàn)閳?zhí)行時(shí)間長(zhǎng),就被降級(jí)了。
而 Hystrix 的默認(rèn)的超時(shí)時(shí)間的最大值就只有 1s。
所以就算你 Feign 超時(shí)時(shí)間設(shè)置的再大,超過(guò) 1s 就算超時(shí),然后被降級(jí),太坑了……
所以我們需要修改這個(gè)默認(rèn)的超時(shí)時(shí)間的最大值,具體的配置項(xiàng)如下:
hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds:30000
并且時(shí)間上大致要符合下面這個(gè)原則:
Hystrix 超時(shí)時(shí)間 >= (連接超時(shí)時(shí)間 + 讀超時(shí)時(shí)間) * 重試次數(shù)
重試次數(shù)我們前面也提到了,雖然一般我們不設(shè)置,但是為了嚴(yán)謹(jǐn)還是得加上,因?yàn)橐淮?Http 接口的執(zhí)行時(shí)間肯定跟重試次數(shù)有關(guān),重試次數(shù)越多,時(shí)間就越長(zhǎng)。
而連接超時(shí)時(shí)間 + 讀超時(shí)時(shí)間設(shè)置方式,前面提到很多次,不論是通過(guò) Feign 本身設(shè)置還是通過(guò) Ribbon 來(lái)設(shè)置,都是可以的。
總結(jié)
今天給大家扒了扒在不同使用條件下 Feign 的超時(shí)時(shí)間設(shè)置,總結(jié)起來(lái)大致如下:
單獨(dú)使用 Feign 時(shí):通過(guò) Feign.Builder 和方法參數(shù);
SpringCloud 環(huán)境下單獨(dú)使用 Feign:方法參數(shù)、配置文件、聲明 Options Bean;
跟 Ribbon 配合使用:通過(guò) Ribbon 的超時(shí)參數(shù)設(shè)置;
跟 Hystrix 配合使用:修改默認(rèn)的超時(shí)時(shí)間,盡量符合 Hystrix 超時(shí)時(shí)間 >= (連接超時(shí)時(shí)間 + 讀超時(shí)時(shí)間) * 重試次數(shù)。
審核編輯:劉清
-
URL
+關(guān)注
關(guān)注
0文章
140瀏覽量
15824 -
負(fù)載均衡
+關(guān)注
關(guān)注
0文章
121瀏覽量
12570 -
HTTP接口
+關(guān)注
關(guān)注
0文章
21瀏覽量
1956
原文標(biāo)題:Feign 如何設(shè)置超時(shí)時(shí)間,不同情況下還真不一樣
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
評(píng)論