1. 回顧java
上文講解了使用註解@HystrixCommand的fallbackMethod屬性實現回退。然而,Feign是以接口形式工做的,react
它沒有方法體,前文講解的方式顯然不適用與Feign。web
事實上,Spring Cloud默認已爲Feign整合了Hystrix,只要Hystrix在項目的classpath中,Feign默認就會用spring
斷路器包裹全部方法。架構
2. 爲Feign添加回退app
> 複製項目 microservice-consumer-movie-feign ,將 ArtifactId 修改成 microservice-consumer-movie-feign-hystrix-fallback。ide
> 將以前編寫的Feign接口修改成以下內容:微服務
package com.itmuch.cloud.microserviceconsumermoviefeignhystrixfallback.feign; import com.itmuch.cloud.microserviceconsumermoviefeignhystrixfallback.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; /** * Feign的fallback測試類 * 使用@FeignClient的fallback屬性指定回退類 */ @FeignClient(name = "microservice-provider-user", fallback = FeignClientFallback.class) public interface UserFeignClient { @GetMapping(value = "/{id}") User findById(@PathVariable("id") Long id); } /** * 回退類FeignClientFallback需實現Feign Client接口 * FeignClientFallback也能夠是public class,沒有區別 */ @Component class FeignClientFallback implements UserFeignClient { @Override public User findById(Long id) { User user = new User(); user.setId(-1L); user.setUsername("默認用戶"); return user; } }
> 修改 application.yml 文件,聲明 Feign 的 Hystrix 支持測試
feign: hystrix: enabled: true
> 啓動 microservice-discovery-eurekaui
> 啓動 microservice-provider-user
> 啓動 microservice-consumer-movie-feign-hystrix-fallback
> 訪問 http://localhost:8010/user/1 ,可正常獲取結果
> 中止 microservice-provider-user
> 訪問 http://localhost:8010/user/1 ,可獲取回退方法裏返回的結果
3. 經過Fallback Factory檢查回退緣由
> 複製項目 microservice-consumer-movie-feign,將 ArtifactId 修改成 microservice-consumer-movie-feign-hystrix-fallback-factory。
> 修改 UserFeignClient.java 爲以下內容
package com.itmuch.cloud.microserviceconsumermoviefeignhystrixfallbackfactory.feign; import com.itmuch.cloud.microserviceconsumermoviefeignhystrixfallbackfactory.pojo.User; import feign.hystrix.FallbackFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(name = "microservice-provider-user", fallbackFactory = FeignClientFallbackFactory.class) public interface UserFeignClient { @GetMapping(value = "/{id}") User findById(@PathVariable("id") Long id); } /** * UserFeignClient的fallbackFactory類,該類須要實現FallbackFactory接口,並覆寫create方法 */ @Component class FeignClientFallbackFactory implements FallbackFactory<UserFeignClient> { private static final Logger LOGGER = LoggerFactory.getLogger(FeignClientFallbackFactory.class); @Override public UserFeignClient create(Throwable throwable) { // 下方匿名方法的Lambda表達式。>= JDK8
return (id) -> { // 日誌最好放在各個fallback方法中,而不要直接放在create方法中。 // 不然在引用啓動時,就會打印該日誌
FeignClientFallbackFactory.LOGGER.info("fallback; reason was: ", throwable); User user = new User(); user.setId(-1L); user.setUsername("默認用戶"); return user; }; /* return new UserFeignClient() { @Override public User findById(Long id) { // 日誌最好放在各個fallback方法中,而不要直接放在create方法中。 // 不然在引用啓動時,就會打印該日誌 FeignClientFallbackFactory.LOGGER.info("fallback; reason was: ", throwable); User user = new User(); user.setId(-1L); user.setUsername("默認用戶"); return user; } };*/ } }
> 修改 application.yml 文件,聲明 Feign 的 Hystrix 支持
feign: hystrix: enabled: true
> 跟上方測試方法同樣。這次調用回退方法時,控制檯會打出錯誤日誌
2018-06-07 13:55:31.716 INFO 5188 --- [provider-user-1] c.i.c.m.f.FeignClientFallbackFactory : fallback; reason was: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: microservice-provider-user at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:71) ~[spring-cloud-openfeign-core-2.0.0.M1.jar:2.0.0.M1] at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:97) ~[feign-core-9.5.1.jar:na] at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-9.5.1.jar:na] at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108) ~[feign-hystrix-9.5.1.jar:na] at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302) ~[hystrix-core-1.5.12.jar:1.5.12] at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298) ~[hystrix-core-1.5.12.jar:1.5.12] at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.3.6.jar:1.3.6] at rx.Observable.unsafeSubscribe(Observable.java:10256) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) ~[rxjava-1.3.6.jar:1.3.6] at rx.Observable.unsafeSubscribe(Observable.java:10256) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.3.6.jar:1.3.6] at rx.Observable.unsafeSubscribe(Observable.java:10256) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OperatorSubscribeOn$SubscribeOnSubscriber.call(OperatorSubscribeOn.java:100) [rxjava-1.3.6.jar:1.3.6] at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:56) [hystrix-core-1.5.12.jar:1.5.12] at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:47) [hystrix-core-1.5.12.jar:1.5.12] at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction.call(HystrixContexSchedulerAction.java:69) [hystrix-core-1.5.12.jar:1.5.12] at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) [rxjava-1.3.6.jar:1.3.6] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_161] at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_161] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_161] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_161] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161] Caused by: com.netflix.client.ClientException: Load balancer does not have available server for client: microservice-provider-user at com.netflix.loadbalancer.LoadBalancerContext.getServerFromLoadBalancer(LoadBalancerContext.java:483) ~[ribbon-loadbalancer-2.2.4.jar:2.2.4] at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:184) ~[ribbon-loadbalancer-2.2.4.jar:2.2.4] at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:180) ~[ribbon-loadbalancer-2.2.4.jar:2.2.4] at rx.Observable.unsafeSubscribe(Observable.java:10256) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:94) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:42) ~[rxjava-1.3.6.jar:1.3.6] at rx.Observable.unsafeSubscribe(Observable.java:10256) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber$1.call(OperatorRetryWithPredicate.java:127) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.enqueue(TrampolineScheduler.java:73) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.schedule(TrampolineScheduler.java:52) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:79) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:45) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.util.ScalarSynchronousObservable$WeakSingleProducer.request(ScalarSynchronousObservable.java:276) ~[rxjava-1.3.6.jar:1.3.6] at rx.Subscriber.setProducer(Subscriber.java:209) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:138) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:129) ~[rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.3.6.jar:1.3.6] at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.3.6.jar:1.3.6] at rx.Observable.subscribe(Observable.java:10352) [rxjava-1.3.6.jar:1.3.6] at rx.Observable.subscribe(Observable.java:10319) [rxjava-1.3.6.jar:1.3.6] at rx.observables.BlockingObservable.blockForSingle(BlockingObservable.java:443) ~[rxjava-1.3.6.jar:1.3.6] at rx.observables.BlockingObservable.single(BlockingObservable.java:340) ~[rxjava-1.3.6.jar:1.3.6] at com.netflix.client.AbstractLoadBalancerAwareClient.executeWithLoadBalancer(AbstractLoadBalancerAwareClient.java:112) ~[ribbon-loadbalancer-2.2.4.jar:2.2.4] at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:63) ~[spring-cloud-openfeign-core-2.0.0.M1.jar:2.0.0.M1] ... 32 common frames omitted 2018-06-07 14:00:17.616 INFO 5188 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
4. 爲Feign禁用Hystrix
上面說過,在Spring Cloud中,只要Hystrix在項目的classpath中,Feign就會使用斷路器包裹Feign客戶端的全部方法。這樣雖然方便,可是不少場景下並不須要該功能。
全局禁用Hystrix,只須在 application.yml 中配置 feign.hystrix.enabled=false 便可
藉助Feign的自定義配置,可輕鬆爲指定名稱的Feign客戶端禁用Hystrix。
> 建立配置類
package com.itmuch.cloud.microserviceconsumermoviefeignhystrixfallbackfactory.config; import feign.Feign; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @Configuration public class FeignDisableHystrixConfiguration { @Bean @Scope("prototype") public Feign.Builder feignBuilder() { return Feign.builder(); } }
> 在想要禁用Hystrix的@FeignClient引用該配置類便可
@FeignClient(name = "microservice-provider-user", configuration = FeignDisableHystrixConfiguration.class) public interface UserFeignClient { }
5. 總結
本文講了Feign使用Hystrix的各類知識。包括使用、檢查回退緣由、禁用Hystrix等。
下文將講解Hystrix的監控。敬請期待~~~
6. 參考
周立 --- 《Spring Cloud與Docker微服務架構與實戰》