天天看點

《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時

目錄

  • ​​1.什麼是Feign逾時​​
  • ​​2.Ribbon逾時​​
  • ​​3.Hystrix逾時​​
  • ​​4.在Feign中禁用Hystrix​​
  • ​​5.同時設定Ribbon逾時和Hystrix逾時​​
  • ​​7.Ribbon逾時值和Hystrix逾時值如何設定?​​
  • ​​7.1.Feign逾時結論​​
  • ​​7.2.ribbon的重試機制​​

1.什麼是Feign逾時

1.使用​

​Feign​

​​調用接口分為兩層,​

​Ribbon​

​​的調用和​

​Hystrix​

​​的調用,是以​

​Ribbon​

​​的逾時時間和​

​Hystrix​

​​的逾時時間的結合就是​

​Feign​

​的逾時時間。

2.一般情況下 都是 ​

​Ribbon​

​​ 的逾時時間(<)​

​Hystrix​

​​的逾時時間(因為涉及到​

​Ribbon​

​​ 的重試機制),如果​

​Ribbon​

​​ 的逾時時間大于Hystrix的逾時時間,對于​

​Ribbon​

​​ 的重試是沒有意義的(​

​Hystrix​

​​逾時熔斷了,​

​Ribbon​

​ 無法重試)。

提示:

  • 預設情況下,如果Ribbon沒有配置重試時間和次數,預設是1S逾時,預設會自動重試1次。
  • 預設情況下,如果Hystrix沒有配置重試時間和次數,預設是1S逾時,預設會自動重試1次。

2.Ribbon逾時

問題: 在服務提供者的接口上加休眠5秒,測試Feign調用的時候是否會逾時?

@GetMapping(value = "/getProductInfo/{productId}")
public String getProductInfo(@PathVariable("productId") String productId) {
    log.info("請求進來啦");
    try {
        log.info("模拟服務端逾時");
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
    }
    return "Hello Nacos Discovery " + productId;
}      

接口提供方和接口調用方的​

​application.yml​

​都是如下:

server:
  port: 8716
spring:
  cloud:
    nacos:
      discovery:
        #服務注冊與發現位址
        server-addr: 127.0.0.1:8848
        #開啟服務注冊與發現功能
        enabled: true
  application:
    name: nacos-product      

通路接口:​​http://localhost:8777/getProduct/100​​​ 控制台異常如下:

《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is feign.RetryableException: Read timed out executing GET http://nacos-product/getProductInfo/100
  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:503) ~[jakarta.servlet-api-4.0.3.jar:4.0.3]
  at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[jakarta.servlet-api-4.0.3.jar:4.0.3]
  at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) ~[undertow-core-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) ~[undertow-core-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) ~[undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) ~[undertow-core-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269) [undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78) [undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133) [undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130) [undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) [undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) [undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249) [undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78) [undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99) [undertow-servlet-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376) [undertow-core-2.0.29.Final.jar:2.0.29.Final]
  at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) [undertow-core-2.0.29.Final.jar:2.0.29.Final]
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_40]
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_40]
  at java.lang.Thread.run(Thread.java:745) [na:1.8.0_40]
Caused by: feign.RetryableException: Read timed out executing GET http://nacos-product/getProductInfo/100
  at feign.FeignException.errorExecuting(FeignException.java:249) ~[feign-core-10.7.4.jar:na]
  at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:120) ~[feign-core-10.7.4.jar:na]
  at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80) ~[feign-core-10.7.4.jar:na]
  at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100) ~[feign-core-10.7.4.jar:na]
  at com.sun.proxy.$Proxy85.getProductInfo(Unknown Source) ~[na:na]
  at com.bruce.controller.OrderController.getProduct(OrderController.java:25) ~[classes/:na]
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_40]
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_40]
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_40]
  at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_40]
  at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879) ~[spring-webmvc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
  ... 47 common frames omitted
Caused by: java.net.SocketTimeoutException: Read timed out
  at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.8.0_40]
  at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) ~[na:1.8.0_40]
  at java.net.SocketInputStream.read(SocketInputStream.java:170) ~[na:1.8.0_40]
  at java.net.SocketInputStream.read(SocketInputStream.java:141) ~[na:1.8.0_40]
  at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) ~[na:1.8.0_40]
  at java.io.BufferedInputStream.read1(BufferedInputStream.java:286) ~[na:1.8.0_40]
  at java.io.BufferedInputStream.read(BufferedInputStream.java:345) ~[na:1.8.0_40]
  at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:704) ~[na:1.8.0_40]
  at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:647) ~[na:1.8.0_40]
  at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1535) ~[na:1.8.0_40]
  at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1440) ~[na:1.8.0_40]
  at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480) ~[na:1.8.0_40]
  at feign.Client$Default.convertResponse(Client.java:78) ~[feign-core-10.7.4.jar:na]
  at feign.Client$Default.execute(Client.java:74) ~[feign-core-10.7.4.jar:na]
  at org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.execute(FeignLoadBalancer.java:93) ~[spring-cloud-openfeign-core-2.2.2.RELEASE.jar:2.2.2.RELEASE]
  at org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.execute(FeignLoadBalancer.java:56) ~[spring-cloud-openfeign-core-2.2.2.RELEASE.jar:2.2.2.RELEASE]
  at com.netflix.client.AbstractLoadBalancerAwareClient$1.call(AbstractLoadBalancerAwareClient.java:104) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
  at com.netflix.loadbalancer.reactive.LoadBalancerCommand$3$1.call(LoadBalancerCommand.java:303) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
  at com.netflix.loadbalancer.reactive.LoadBalancerCommand$3$1.call(LoadBalancerCommand.java:287) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
  at rx.internal.util.ScalarSynchronousObservable$3.call(ScalarSynchronousObservable.java:231) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.util.ScalarSynchronousObservable$3.call(ScalarSynchronousObservable.java:228) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OnSubscribeConcatMap$ConcatMapSubscriber.drain(OnSubscribeConcatMap.java:286) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OnSubscribeConcatMap$ConcatMapSubscriber.onNext(OnSubscribeConcatMap.java:144) ~[rxjava-1.3.8.jar:1.3.8]
  at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:185) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
  at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:180) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
  at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:94) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:42) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber$1.call(OperatorRetryWithPredicate.java:127) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.enqueue(TrampolineScheduler.java:73) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.schedule(TrampolineScheduler.java:52) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:79) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:45) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.util.ScalarSynchronousObservable$WeakSingleProducer.request(ScalarSynchronousObservable.java:276) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.Subscriber.setProducer(Subscriber.java:209) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:138) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:129) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.Observable.subscribe(Observable.java:10423) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.Observable.subscribe(Observable.java:10390) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.observables.BlockingObservable.blockForSingle(BlockingObservable.java:443) ~[rxjava-1.3.8.jar:1.3.8]
  at rx.observables.BlockingObservable.single(BlockingObservable.java:340) ~[rxjava-1.3.8.jar:1.3.8]
  at com.netflix.client.AbstractLoadBalancerAwareClient.executeWithLoadBalancer(AbstractLoadBalancerAwareClient.java:112) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
  at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:83) ~[spring-cloud-openfeign-core-2.2.2.RELEASE.jar:2.2.2.RELEASE]
  at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:110) ~[feign-core-10.7.4.jar:na]
  ... 64 common frames omitted      

分析: 出現這個異常的時候,起初我不知道究竟是​

​Hystrix​

​​斷路器走熔斷了還是​

​Ribbon​

​​處理請求逾時了,通過觀察日志和做測試,我發現了錯誤日志是由: ​

​org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer​

​​包列印出來的,那麼我就很确定這是​

​Ribbon​

​​處理請求逾時了,因為我設定了接口提供方休眠​

​5​

​​秒,而Ribbon的預設逾時是​

​1​

​​秒,而預設情況下在使用Feign時,​

​Hystrix預設是未開啟的​

​,這樣就更加肯定這是Ribbon逾時,那麼如何處理呢,見後文。

org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.execute(FeignLoadBalancer.java:93) ~[spring-cloud-openfeign-core-2.1.2.RELEASE.jar:2.1.2.RELEASE]      

解決: 服務調用方​

​application.yml​

​​加入​

​Ribbon​

​​請求處理逾時和連接配接逾時,這裡需要注意一下,我們在服務提供方的接口裡設定休眠的時間是5秒,是以這裡在設定​

​Ribbon​

​的逾時時間時,需要大于5秒,不然起不到作用。

server:
  port: 8777
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        enabled: true
  application:
    name: nacos-order
# 調用端比對值
ribbon:
  ReadTimeout: 6000
  ConnectTimeout: 5000      

再次請求:​​http://localhost:8777/getProduct/100​​​

《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時

正如我們初步的判斷,上述異常在Feign消費服務,​

​Feign​

​中的​

​Hystrix​

​預設是未開啟的,是以這個問題是由Ribbon逾時引起的。

3.Hystrix逾時

問題: 在服務提供者的接口上加休眠5秒,測試Feign調用的時候是否會逾時?

@GetMapping(value = "/getProductInfo/{productId}")
public String getProductInfo(@PathVariable("productId") String productId) {
    log.info("請求進來啦");
    try {
        log.info("模拟服務端逾時");
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
    }
    return "Hello Nacos Discovery " + productId;
}      

接口提供方的​

​application.yml​

​​配置改成如下,為了探究​

​Hystrix​

​​我們去掉​

​Ribbon​

​​的逾時配置,開啟​

​Feign​

​​中的​

​Hystrix​

​​:

《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時

通路接口:http://localhost:8777/getProduct/100 控制台異常如下:

2021-02-20 10:44:54.878 ERROR 8012 --- [ HystrixTimer-1] c.b.client.OrderFeignFallbackFactory     : 調用異常:com.netflix.hystrix.exception.HystrixTimeoutException
2021-02-20 10:44:54.881  INFO 8012 --- [  XNIO-1 task-1] com.bruce.controller.OrderController     : 調用服務結束: 開啟斷路-fallback; reason was: com.netflix.hystrix.exception.HystrixTimeoutException
2021-02-20 10:44:55.075  INFO 8012 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: nacos-product.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647      
《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時

控制台列印日志:

《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時

分析: 從異常的日志來看,服務調用逾時了,逾時後​

​Hystrix​

​​立即進行了服務的熔斷,走了回調方法​

​OrderFeignFallbackFactory​

​,輸出了服務熔斷後我們定制的Msg,那麼如何解決這個逾時問題呢?

package com.bruce.client;

import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @BelongsProject: springcloud-alibaba-nacos
 * @BelongsPackage: com.bruce.client
 * @CreateTime: 2021-02-20 10:42
 * @Description: TODO
 */
@Component
@Slf4j
public class OrderFeignFallbackFactory implements FallbackFactory<OrderFeign> {
    @Override
    public OrderFeign create(final Throwable throwable) {
        log.error("調用異常:" + throwable.toString());
        return new OrderFeign() {
            @Override
            public String getProductInfo(String string) {
                return "開啟斷路-fallback; reason was: " + throwable.toString();
            }
        };
    }
}      

解決: 在接口調用方​

​application.yml​

​​加入​

​Hystrix​

​​逾時配置,同樣這裡需要注意的是,​

​Hystrix​

​​預設的逾時時間是​

​1​

​​秒,而我們在服務提供方的接口裡設定休眠的時間是5秒,是以這裡在設定Hystrix的逾時時間時,需要大于​

​5​

​秒,不然起不到作用防止逾時的作用。

server:
  port: 8777
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        enabled: true
  application:
    name: nacos-order

feign:
  hystrix:
    enabled: true

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 60000

#ribbon:
#  ReadTimeout: 6000
#  ConnectTimeout: 5000      

再次通路接口:http://localhost:8777/getProduct/100 控制台異常如下:

《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時

控制台異常如下:

《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時

分析: 開啟了Hystrix斷路器并且設定了​

​Hystrix​

​​的逾時時間為6秒,但是這個時候Ribbon處理請求卻逾時了。分析下面這行異常日志:​

​RetryableException​

​​ 大概意思是重試異常,查閱資料得知,Ribbon在逾時後會進行重試的,因為沒有對​

​Ribbon​

​​逾時進行處理,但是開啟了​

​Hystirx​

​​的斷路器功能和設定其逾時,是以​

​Ribbon​

​​在處理請求逾時的時候,Hystirx就出來起作用了,對服務進行熔斷,降級處理,進而進入回調方法​

​OrderFeignFallbackFactory​

​,傳回友好提示起到服務保護作用。

調用異常:feign.RetryableException: Read timed out executing GET http://nacos-product/getProductInfo/1010110      

總結: 通過上述分析,那麼在開啟Hystrix斷路器功能之後就需要對​

​Ribbon​

​​進行逾時設定了,兩者在碰到Feign時,需要協同作戰,或者更粗暴點直接不開啟Hystrix斷路器功能,但是這種方式極不推薦,高并發的項目中服務保護必不可少,是以我們需要将​

​Ribbon​

​​與Hystrix結合使用,那麼接下來我們需要着重讨論​

​Ribbon​

​​與​

​Hystrix​

​如何協同作戰。

4.在Feign中禁用Hystrix

禁用Hystrix:

參考官網資料: ​​https://cloud.spring.io/spring-cloud-static/spring-cloud-openfeign/2.2.2.RELEASE/reference/html/#spring-cloud-feign-hystrix​​

要基于每個用戶端禁用​

​Hystrix​

​​支援,請建立​

​Feign.Builder​

​​具有“原型”範圍的香草,這段話摘自官網,例如:

《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時
package com.bruce.client;

import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class FooConfiguration {
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }
}      

5.同時設定Ribbon逾時和Hystrix逾時

​Feign​

​​調用時同時設定​

​Ribbon​

​​逾時和​

​Hystrix​

​​逾時,​

​application.yml​

​配置如下

server:
  port: 8777
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        enabled: true
  application:
    name: nacos-order

feign:
  hystrix:
    enabled: true
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 120000
ribbon:
  ReadTimeout: 6000
  ConnectTimeout: 5000      

通路接口: ​​http://localhost:8777/getProduct/100​​​

《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時
《SpringCloud Alibaba 微服務架構》專題(十)-Spring Cloud Alibaba之Feign調用Ribbon+Hystrix請求逾時

7.Ribbon逾時值和Hystrix逾時值如何設定?

注意: Ribbon逾時+Hystrix逾時= Feign逾時

7.1.Feign逾時結論

  1. 使用​

    ​Feign​

    ​​調用接口分兩層,​

    ​ribbon​

    ​​的調用和​

    ​hystrix​

    ​​的調用,是以​

    ​ribbon​

    ​​的逾時時間和​

    ​Hystrix​

    ​​的逾時時間的結合就是​

    ​Feign​

    ​的逾時時間
  2. 一般情況下 都是​

    ​ribbon的逾時時間​

    ​​(<)​

    ​hystrix的逾時時間​

    ​​(因為涉及到ribbon的重試機制),如果ribbon的逾時時間大于​

    ​hystrix​

    ​​的逾時時間,對于​

    ​ribbon​

    ​​的重試是沒有意義的(​

    ​hystrix​

    ​​逾時熔斷了,​

    ​ribbon​

    ​​無法重試),預設我項目中是沒有配置​

    ​ribbon​

    ​​的重試的,但是需要注意的是無論是否需要配置ribbon逾時時的重試次數,​

    ​hystrix​

    ​​的逾時都是需要大于​

    ​ribbon​

    ​的逾時。

7.2.ribbon的重試機制

ribbon:
  OkToRetryOnAllOperations: false #對所有操作請求都進行重試,預設false
  ReadTimeout: 5000   #負載均衡逾時時間,預設值5000
  ConnectTimeout: 2000 #ribbon請求連接配接的逾時時間,預設值2000
  MaxAutoRetries: 0     #對目前執行個體的重試次數,預設0
  MaxAutoRetriesNextServer: 1 #對切換執行個體的重試次數,預設1
hystrix:
  command:
    default:  #default全局有效,service id指定應用有效
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 10000 #斷路器逾時時間,預設1000ms