一次生產(chǎn)Feign重試問題的排查過(guò)程

問題

在使用skywalking監(jiān)控生產(chǎn)環(huán)境問題時(shí)龄毡,發(fā)現(xiàn)有一個(gè)請(qǐng)求的調(diào)用鏈路中微服務(wù)Platform的內(nèi)部接口inner/userinfo很奇怪的被重復(fù)調(diào)用了2次哼蛆,如下圖:

image-20190703230049833.png

分析過(guò)程

相關(guān)版本:Spring Cloud版本為Dalston.SR4,Spring Boot版本為1.5.7.RELEASE

查看inner/userinfo的服務(wù)方的2次調(diào)用分別耗時(shí)6043ms和8078ms骚勘,通過(guò)跟蹤RetryableFeignLoadBalancer類的execute方法發(fā)現(xiàn),feign的連接超時(shí)時(shí)間connectTimeout=2000(2秒),讀超時(shí)時(shí)間readTimeout=5000(5秒)纳击,因此可以判斷服務(wù)方的2次響應(yīng)時(shí)間超過(guò)讀超時(shí)時(shí)間閾值5秒了,因此調(diào)用方最終報(bào)了超時(shí)異常RetryableException攻臀。

我們知道焕数,Spring Cloud中Feign整合了Ribbon,但Feign和Ribbon都有重試的功能刨啸,Spring Cloud為了統(tǒng)一兩者的行為堡赔,將Feign的重試策略默認(rèn)設(shè)置為 feign.Retryer#NEVER_RETRY(即永不重試)。如要使用Feign的重試功能的話设联,只需使用Ribbon的重試配置即可善已。既然這樣,那為什么會(huì)有以上現(xiàn)象呢仑荐?

進(jìn)一步研究發(fā)現(xiàn)雕拼,對(duì)于Camden以及以后的版本,F(xiàn)eign的重試可使用如下屬性進(jìn)行配置:

ribbon:
  # 同一實(shí)例最大重試次數(shù)粘招,不包括首次調(diào)用啥寇。默認(rèn)值為0
  MaxAutoRetries: 0
  # 同一個(gè)微服務(wù)其他實(shí)例的最大重試次數(shù),不包括第一次調(diào)用的實(shí)例。默認(rèn)值為1
  MaxAutoRetriesNextServer: 1
  # 是否所有操作(GET辑甜、POST等)都允許重試衰絮。默認(rèn)值為false
  OkToRetryOnAllOperations: false

我們的服務(wù)方Platform沒有配置這些參數(shù),因此應(yīng)該是使用了默認(rèn)值磷醋。因?yàn)槲覀働latform微服務(wù)只啟動(dòng)了一個(gè)實(shí)例猫牡,所以我重點(diǎn)關(guān)注MaxAutoRetries參數(shù),實(shí)際跟蹤發(fā)現(xiàn)它的值也是0邓线,這跟我理解的有出入疤视选!

跟蹤Spring Cloud的源碼骇陈,發(fā)現(xiàn)Feign是在RetryTemplate的doExecute方法中進(jìn)行重試的判斷和調(diào)用的:

protected <T, E extends Throwable> T doExecute(...) {
    ...
        while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {

                try {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Retry: count=" + context.getRetryCount());
                    }
                    // Reset the last exception, so if we are successful
                    // the close interceptors will not think we failed...
                    lastException = null;
                    return retryCallback.doWithRetry(context);
                }
                catch (Throwable e) {
          ...
        }
      ...
    }
  ...
}

其中canRetry方法用于判斷當(dāng)接口調(diào)用異常時(shí)是否需要進(jìn)行重試碟绑。

跟進(jìn)canRetry方法殉农,終于找到了罪魁禍?zhǔn)住狪nterceptorRetryPolicy的canRetry方法:

org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy.java

    @Override
    public boolean canRetry(RetryContext context) {
        LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext)context;
        if(lbContext.getRetryCount() == 0  && lbContext.getServiceInstance() == null) {
            //We haven't even tried to make the request yet so return true so we do
            lbContext.setServiceInstance(serviceInstanceChooser.choose(serviceName));
            return true;
        }
        return policy.canRetryNextServer(lbContext);
    }

留意最后一行:policy.canRetryNextServer(lbContext)橱赠。這一行意思是烤宙,根據(jù)策略判斷是否重試服務(wù)的下一個(gè)實(shí)例。因?yàn)镸axAutoRetriesNextServer默認(rèn)值為1婿崭,因此這里會(huì)返回true拨拓,所以inner/userinfo就被調(diào)用了2次。

解決方案

找到原因就好辦了氓栈,解決方案很簡(jiǎn)單渣磷,在調(diào)用方的yml配置MaxAutoRetriesNextServer的值為0即可:

ribbon:
  # 同一實(shí)例最大重試次數(shù),不包括首次調(diào)用颤绕。默認(rèn)值為0
  MaxAutoRetries: 0
  # 同一個(gè)微服務(wù)其他實(shí)例的最大重試次數(shù)幸海,不包括第一次調(diào)用的實(shí)例。默認(rèn)值為1
  MaxAutoRetriesNextServer: 0
  # 是否所有操作(GET奥务、POST等)都允許重試物独。默認(rèn)值為false
  OkToRetryOnAllOperations: false

配置后,在開發(fā)環(huán)境驗(yàn)證成功氯葬!

附錄

相關(guān)節(jié)點(diǎn)的異常:

1挡篓、Hystrix/IUserService#getUserInfo()/Execution節(jié)點(diǎn)的異常信息為:

Read timed out executing GET http://prong-cloud-server-platform/inner/userinfo

feign.RetryableException: Read timed out executing GET http://prong-cloud-server-platform/inner/userinfo
at feign.FeignException.errorExecuting(FeignException.java:67)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:104)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
at feign.hystrix.HystrixInvocationHandler$1.run$original$guV1ukWA(HystrixInvocationHandler.java:108)
at feign.hystrix.HystrixInvocationHandler$1.run$original$guV1ukWA$accessor$1jiMEUrR(HystrixInvocationHandler.java)
at feign.hystrix.HystrixInvocationHandler$1$auxiliary$sEUgyNk3.call(Unknown Source)
at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:93)
at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:10211)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)
at rx.Observable.unsafeSubscribe(Observable.java:10211)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:10211)
at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:56)
at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:47)
at io.prong.autoconfigure.auth.service.ProngHystrixConcurrencyStrategy$WrappedCallable.call(ProngHystrixConcurrencyStrategy.java:121)
at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction.call(HystrixContexSchedulerAction.java:69)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
java.net.SocketTimeoutException: Read timed out
at feign.FeignException.errorExecuting(FeignException.java:67)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:104)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
at feign.hystrix.HystrixInvocationHandler$1.run$original$guV1ukWA(HystrixInvocationHandler.java:108)
at feign.hystrix.HystrixInvocationHandler$1.run$original$guV1ukWA$accessor$1jiMEUrR(HystrixInvocationHandler.java)
at feign.hystrix.HystrixInvocationHandler$1$auxiliary$sEUgyNk3.call(Unknown Source)
at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:93)
at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)

2、/inner/userinfo節(jié)點(diǎn)的異常信息為:

java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:735)
at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:678)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1587)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
at feign.Client$Default.convertResponse(Client.java:152)
at feign.Client$Default.execute$original$Z12I5rH3(Client.java:74)
at feign.Client$Default.execute$original$Z12I5rH3$accessor$MeGe8flP(Client.java)
at feign.Client$Default$auxiliary$uXNNBULq.call(Unknown Source)
at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:93)
at feign.Client$Default.execute(Client.java)
at org.springframework.cloud.netflix.feign.ribbon.RetryableFeignLoadBalancer$1.doWithRetry(RetryableFeignLoadBalancer.java:92)
at org.springframework.cloud.netflix.feign.ribbon.RetryableFeignLoadBalancer$1.doWithRetry(RetryableFeignLoadBalancer.java:77)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:287)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:164)
at org.springframework.cloud.netflix.feign.ribbon.RetryableFeignLoadBalancer.execute(RetryableFeignLoadBalancer.java:77)
at org.springframework.cloud.netflix.feign.ribbon.RetryableFeignLoadBalancer.execute(RetryableFeignLoadBalancer.java:48)
at com.netflix.client.AbstractLoadBalancerAwareClient$1.call(AbstractLoadBalancerAwareClient.java:109)
at com.netflix.loadbalancer.reactive.LoadBalancerCommand$3$1.call(LoadBalancerCommand.java:303)
at com.netflix.loadbalancer.reactive.LoadBalancerCommand$3$1.call(LoadBalancerCommand.java:287)
at rx.internal.util.ScalarSynchronousObservable$3.call(ScalarSynchronousObservable.java:231)
at rx.internal.util.ScalarSynchronousObservable$3.call(ScalarSynchronousObservable.java:228)
at rx.Observable.unsafeSubscribe(Observable.java:10211)
at rx.internal.operators.OnSubscribeConcatMap$ConcatMapSubscriber.drain(OnSubscribeConcatMap.java:286)
at rx.internal.operators.OnSubscribeConcatMap$ConcatMapSubscriber.onNext(OnSubscribeConcatMap.java:144)
at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:185)
at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:180)
at rx.Observable.unsafeSubscribe(Observable.java:10211)
at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:94)
at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:42)
at rx.Observable.unsafeSubscribe(Observable.java:10211)
at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber$1.call(OperatorRetryWithPredicate.java:127)
at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.enqueue(TrampolineScheduler.java:73)
at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.schedule(TrampolineScheduler.java:52)
at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:79)
at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:45)
at rx.internal.util.ScalarSynchronousObservable$WeakSingleProducer.request(ScalarSynchronousObservable.java:276)
at rx.Subscriber.setProducer(Subscriber.java:209)
at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:138)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帚称,一起剝皮案震驚了整個(gè)濱河市官研,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌闯睹,老刑警劉巖戏羽,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異楼吃,居然都是意外死亡始花,警方通過(guò)查閱死者的電腦和手機(jī)妄讯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)酷宵,“玉大人亥贸,你說(shuō)我怎么就攤上這事〗娇眩” “怎么了炕置?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)男韧。 經(jīng)常有香客問我朴摊,道長(zhǎng),這世上最難降的妖魔是什么煌抒? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任仍劈,我火速辦了婚禮,結(jié)果婚禮上寡壮,老公的妹妹穿的比我還像新娘。我一直安慰自己讹弯,他們只是感情好况既,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著组民,像睡著了一般棒仍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上臭胜,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天莫其,我揣著相機(jī)與錄音,去河邊找鬼耸三。 笑死乱陡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仪壮。 我是一名探鬼主播憨颠,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼积锅!你這毒婦竟也來(lái)了爽彤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缚陷,失蹤者是張志新(化名)和其女友劉穎适篙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箫爷,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嚷节,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年铆铆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丹喻。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡薄货,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碍论,到底是詐尸還是另有隱情谅猾,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布鳍悠,位于F島的核電站税娜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏藏研。R本人自食惡果不足惜敬矩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蠢挡。 院中可真熱鬧弧岳,春花似錦、人聲如沸业踏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)勤家。三九已至腹尖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伐脖,已是汗流浹背热幔。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留讼庇,地道東北人绎巨。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像巫俺,于是被迫代替她去往敵國(guó)和親认烁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容