在上周在的微供有數項目中(數據產品),需要對接企業微信中第三方應用,在使用Feign的去調用微服務的用戶模塊用微信的code獲取access_token以及用戶工廠信息時出現Feign重試超時報錯的情況,通過此篇文章記錄問題解決的過程。
一.問題重現:
1.SpringCloud部分依賴如下
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency>
2.微信相關的接口文檔:
前端通過企業id,配置好回調域名之后,調用微信的Api去獲取code
見文檔https://work.weixin.qq.com/api/doc/90000/90135/91022
注意:
code只能用一次,見文檔,因此獲取到的access_token需要緩存起來,項目中是緩存到redis中的,用於后續的消息推送等等功能
3.請求流程圖
二.原因分析
1.整個請求的鏈路中,階段2是feign請求的位置,但是yml配置文件中並沒有配置feign,因此可以斷定feign使用的默認的配置,問題發生時,查看feign的文檔發現,feign重試默認超時時間是1s,
因此現在重新配置feign的超時時間,現有feign的配置如下
feign:
client:
config:
organization:
connectTimeout: 5000
readTimeout: 5000
其實organization表示的就是feign所調用的服務名稱
connectTimeout表示建立請求連接的連接的時間(這里面包括獲取請求eureka中保存的服務列表-推測)
readTimeout表示連接建立以后請求調用的時間
2.在上述配置中,通過查看organization和data服務的請求日志,發現請求都能順利的建立,但是當階段三去請求微信的接口一旦延遲,則會觸發feign的重試進行第二次調用;
由於階段三請求微信的接口並不是沒有調用,而是由於網絡或者其他原因導致的微信沒有響應,但是code又已經被消費了,當階段二攜帶同樣的code去調用微信的接口,這時就會出現
code已經被消費
3.此時有另外一個問題就是,項目中的服務都是單實例部署,springcloud組件中feign和ribbon都有重試的功能,
Spring Cloud中Feign整合了Ribbon,但Feign和Ribbon都有重試的功能,Spring Cloud為了統一兩者的行為,在C版本以后,將Feign的重試策略默認設置為 feign.Retryer#NEVER_RETRY
(即永不重試)
因此Feign的調用本質還是通過ribbon去實現
feign:
client:
config:
eureka-client:
connectTimeout: 1000
readTimeout: 1000
配置二:
只配置ri'bbon
注意 這里有個坑MaxAutoRetriesNextServer這個參數如果不配置為0,即使在單實例部署的情況下,仍然會發生重試1次,因此如果不想發生重試,則需要手動配置
MaxAutoRetriesNextServer=0和MaxAutoRetries=0
ribbon:
ReadTimeout: 4000
ConnectionTimeout: 4000
OkToRetryOnAllOperations: true
MaxAutoRetriesNextServer: 0 # 當前實例全部失敗后可以換1個實例再重試,
MaxAutoRetries: 1 # 在當前實例只重試2次
配置三:
feign和ribbon都不配置,注意,經過測試后發現這里使用的是ribbon默認的超時配置,配置如下:
MaxAutoRetriesNextServer=1
MaxAutoRetries=0
public LoadBalancerContext(ILoadBalancer lb) { this.clientName = "default"; this.maxAutoRetriesNextServer = 1; this.maxAutoRetries = 0; this.defaultRetryHandler = new DefaultLoadBalancerRetryHandler(); this.okToRetryOnAllOperations = DefaultClientConfigImpl.DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS; this.lb = lb; }
版本號:SpringCloud Dalston.SR1 與 Greenwich.SR1的測試結論一致
注意: Dalston.SR1 ribbon組件默認的超時時間
public static final int DEFAULT_READ_TIMEOUT = 5000;
public static final int DEFAULT_CONNECT_TIMEOUT = 2000;
Greenwich.SR1 ribbon組件默認的超時間
public static final int DEFAULT_CONNECT_TIMEOUT = 1000; public static final int DEFAULT_READ_TIMEOUT = 1000;