Search code examples
springspring-boothystrixspring-cloud-netflix

@FeignClient Not Falling Back


I am trying to learn about Spring Boot and Hystrix and having trouble getting the fallback method to work. I have tried two approaches, @HystrixCommand and @FeignClient. I can get the @HystrixCommand but not @FeignClient. Most of the code is based on examples I have seen while searching so I think I am very close but must be missing something crucial.

Here is the Spring Boot application and REST controller. I am trying both approaches in the same application. It is registering with Eureka. I do not have a hello-world service running so it should always fallback.

package com.example.goodBye.service;

@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
@SpringBootApplication
public class GoodByeServiceApplication {

    private final static AtomicInteger counter = new AtomicInteger();
    private final static Logger logger = LoggerFactory.getLogger(GoodByeServiceApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(GoodByeServiceApplication.class, args);
    }

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }

    @RefreshScope
    @RestController
    class MessageController {

        @Autowired
        private HelloClient helloClient;

        @Autowired
        private HelloService helloService;

        @Value("${message:Good Bye Default}")
        private String message;

        @RequestMapping(value = "/message", produces = "application/json")
        public Message getMessage(@RequestParam(value = "name", defaultValue = "Anonymous") String name) {
            return new Message(counter.incrementAndGet(), String.format("%s from %s", this.message, name));
        }

        @RequestMapping(value = "/hello", produces = "application/json")
        public Message sayHello(@RequestParam(value = "name", defaultValue = "Creepy") String name) {
            Message hello = helloService.sayHello(name);

            return new Message(counter.incrementAndGet(), String.format("%s/%s from %s", hello.getMessage(), message, name));
        }

        @RequestMapping(value = "/helloAgain", produces = "application/json")
        public Message sayHelloAgain(@RequestParam(value = "name", defaultValue = "Creepy") String name) {
            Message hello = helloClient.sayHello(name);

            return new Message(counter.incrementAndGet(), String.format("%s/%s from %s", hello.getMessage(), message, name));
        }

    }

    static class Message {

        private int count;
        private String message;

        public Message() {
        }

        public Message(int count, String message) {
            this.count = count;
            this.message = message;
        }

        public int getCount() {
            return count;
        }

        public String getMessage() {
            return message;
        }

    }

}

Here is the attempt with @HystrixCommand that does work. When I put a break-point in the fallback method I do see Hystrix in the call stack.

@Service
class HelloService {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "rude")
    public Message sayHello(String name) {
        Message ret = null;

        List<ServiceInstance> instances = discoveryClient.getInstances("hello-world");
        ...
        ResponseEntity<Message> resp = restTemplate.getForEntity(uri, Message.class, params);
        ret = resp.getBody();

        return ret;
    }

    public Message rude(String name) {
        return new Message(counter.incrementAndGet(), String.format("%s was rather rude!", name));
    }

}

Here is the attampt with @FeignClient that does not work. The program throws a com.netflix.client.ClientException: Load balancer does not have available server for client: hello-world and the call stack does not include any Hystrix classes.

@FeignClient(value = "hello-world", fallback = HelloClientFallback.class)
interface HelloClient {

    @RequestMapping(value = "/message", method = RequestMethod.GET)
    public Message sayHello(String name);

}

@Component
class HelloClientFallback implements HelloClient {

    @Override
    public Message sayHello(String name) {
        return new Message(counter.incrementAndGet(), String.format("%s is not much better", name));
    }

}

I would appreciate any help. It is probably something stupid but have been staring at it so long I don't see it.

Thanks, Wes.

Here is the failed call stack in case that helps.

com.netflix.client.ClientException: Load balancer does not have available server for client: hello-world
    at com.netflix.loadbalancer.LoadBalancerContext.getServerFromLoadBalancer(LoadBalancerContext.java:483) ~[ribbon-loadbalancer-2.2.2.jar:2.2.2]
    at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:184) ~[ribbon-loadbalancer-2.2.2.jar:2.2.2]
    at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:180) ~[ribbon-loadbalancer-2.2.2.jar:2.2.2]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:94) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:42) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber$1.call(OperatorRetryWithPredicate.java:127) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.enqueue(TrampolineScheduler.java:73) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.schedule(TrampolineScheduler.java:52) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:79) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:45) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.util.ScalarSynchronousObservable$WeakSingleProducer.request(ScalarSynchronousObservable.java:276) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.Subscriber.setProducer(Subscriber.java:209) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:138) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:129) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.subscribe(Observable.java:10307) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.subscribe(Observable.java:10274) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.observables.BlockingObservable.blockForSingle(BlockingObservable.java:445) ~[rxjava-1.1.10.jar:1.1.10]
    at rx.observables.BlockingObservable.single(BlockingObservable.java:342) ~[rxjava-1.1.10.jar:1.1.10]
    at com.netflix.client.AbstractLoadBalancerAwareClient.executeWithLoadBalancer(AbstractLoadBalancerAwareClient.java:117) ~[ribbon-loadbalancer-2.2.2.jar:2.2.2]
    at org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:63) ~[spring-cloud-netflix-core-1.3.0.RC1.jar:1.3.0.RC1]
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:97) ~[feign-core-9.4.0.jar:na]
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-9.4.0.jar:na]
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) ~[feign-core-9.4.0.jar:na]
    at com.example.goodBye.service.$Proxy108.sayHello(Unknown Source) ~[na:na]
    at com.example.goodBye.service.GoodByeServiceApplication$MessageController.sayHelloAgain(GoodByeServiceApplication.java:79) ~[main/:na]

Here is the successful call stack but doesn't really reveals anything useful since Hystrix is running in a different thread.

java.lang.IllegalStateException: Call stack of successful fallback
    at com.example.goodBye.service.GoodByeServiceApplication$HelloService.rude(GoodByeServiceApplication.java:147) ~[main/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_45]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_45]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_45]
    at com.netflix.hystrix.contrib.javanica.command.MethodExecutionAction.execute(MethodExecutionAction.java:116) [hystrix-javanica-1.5.10.jar:1.5.10]
    at com.netflix.hystrix.contrib.javanica.command.MethodExecutionAction.executeWithArgs(MethodExecutionAction.java:93) [hystrix-javanica-1.5.10.jar:1.5.10]
    at com.netflix.hystrix.contrib.javanica.command.GenericCommand$2.execute(GenericCommand.java:76) [hystrix-javanica-1.5.10.jar:1.5.10]
    at com.netflix.hystrix.contrib.javanica.command.AbstractHystrixCommand.process(AbstractHystrixCommand.java:145) [hystrix-javanica-1.5.10.jar:1.5.10]
    at com.netflix.hystrix.contrib.javanica.command.GenericCommand.getFallback(GenericCommand.java:71) [hystrix-javanica-1.5.10.jar:1.5.10]
    at com.netflix.hystrix.HystrixCommand$3.call(HystrixCommand.java:322) [hystrix-core-1.5.10.jar:1.5.10]
    at com.netflix.hystrix.HystrixCommand$3.call(HystrixCommand.java:318) [hystrix-core-1.5.10.jar:1.5.10]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) [rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:142) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87) [rxjava-1.1.10.jar:1.1.10]
    at com.netflix.hystrix.AbstractCommand$HystrixObservableTimeoutOperator$3.onError(AbstractCommand.java:1194) [hystrix-core-1.5.10.jar:1.5.10]
    at rx.internal.operators.OperatorSubscribeOn$1$1.onError(OperatorSubscribeOn.java:59) [rxjava-1.1.10.jar:1.1.10]
    at rx.observers.Subscribers$5.onError(Subscribers.java:230) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87) [rxjava-1.1.10.jar:1.1.10]
    at rx.observers.Subscribers$5.onError(Subscribers.java:230) [rxjava-1.1.10.jar:1.1.10]
    at com.netflix.hystrix.AbstractCommand$DeprecatedOnRunHookApplication$1.onError(AbstractCommand.java:1431) [hystrix-core-1.5.10.jar:1.5.10]
    at com.netflix.hystrix.AbstractCommand$ExecutionHookApplication$1.onError(AbstractCommand.java:1362) [hystrix-core-1.5.10.jar:1.5.10]
    at rx.observers.Subscribers$5.onError(Subscribers.java:230) [rxjava-1.1.10.jar:1.1.10]
    at rx.observers.Subscribers$5.onError(Subscribers.java:230) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeThrow.call(OnSubscribeThrow.java:44) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeThrow.call(OnSubscribeThrow.java:28) [rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) [rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) [rxjava-1.1.10.jar:1.1.10]
    at rx.Observable.unsafeSubscribe(Observable.java:10211) [rxjava-1.1.10.jar:1.1.10]
    at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94) [rxjava-1.1.10.jar:1.1.10]
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:56) [hystrix-core-1.5.10.jar:1.5.10]
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:47) [hystrix-core-1.5.10.jar:1.5.10]
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction.call(HystrixContexSchedulerAction.java:69) [hystrix-core-1.5.10.jar:1.5.10]
    at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) [rxjava-1.1.10.jar:1.1.10]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_45]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_45]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_45]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_45]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_45]

Solution

  • Your logs show that you're using spring cloud netflix 1.3.0RC1. From this version FeignClient doesn't wrap method with HystrixCommand. You need to add hystrix on classpath if doesn't exist and set option feign.hystrix.enabled=true.

    Feign clients no longer wrap methods in Hystrix commands by default (#1277). You must have Hystrix on the classpath and also set feign.hystrix.enabled=true to have Feign automatically wrap methods in Hystrix commands.

    Please refer to release notes. https://github.com/spring-projects/spring-cloud/wiki/Spring-Cloud-Dalston-Release-Notes