Feign call error: failed and no fallback available

Keywords: JSON Apache codec Spring

timed-out and no fallback

This error basically occurs in the Hystrix fuse. The function of the fuse is to determine whether the service can be connected. If it is connected, it doesn't matter. If the call exceeds the time limit within the specified time, it will return an error through the fuse.

Generally, you can set one of the following configurations:

1. Set the time long

Set 5 seconds here

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
2. Turn off the timeout exception property

hystrix.command.default.execution.timeout.enabled=false
3. Disable feign's hystrix

feign.hystrix.enabled: false
failed and no fallback available:

However, the above settings only aim at the wrong closing of the fuse, which does not solve the fundamental problem. For example, when Feign client calls the remote service, the default time is 8 seconds. If it does not return within the specified time, it will also jump to the fuse for processing. Even if the fuse is turned off, the total error handling will still have this problem.

To solve the fundamental problem, we need to start with the request timeout. Because some services may have a long invocation time, we can directly configure:

ribbon.ReadTimeout=60000 ribbon.ConnectTimeout=60000 These are the real solutions to the problem of request timeout. If this is not set, Read Timeout on Request will appear when the called interface is slow.

The number of retries for call failure can also be set:

ribbon.maxAutoRetries=0

failed and no fallback available

For failed and no fallback available, the reason for this exception is that the project is open to fuse: feign.hystrix.enabled: true

The above exception is thrown when the service is called without defining the fallback method. This leads to the first solution.

@FeignClient plus fallback method, and get exception information

There are two ways to add the fallback method to the interface decorated by @ FeignClient. To get exception information, use the fallbackFactory method:

@FeignClient(name = "serviceId", fallbackFactory = TestServiceFallback.class)
public interface TestService {
    @RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
    Result get(@PathVariable("id") Integer id);
}

Specify fallbackFactory in the @ FeignClient annotation. In the above example, TestServiceFallback:

import feign.hystrix.FallbackFactory;
import org.apache.commons.lang3.StringUtils;
@Component
public class TestServiceFallback implements FallbackFactory<TestService> {
    private static final Logger LOG = LoggerFactory.getLogger(TestServiceFallback.class);
    public static final String ERR_MSG = "Test Interface is temporarily unavailable: ";
    @Override
    public TestService create(Throwable throwable) {
        String msg = throwable == null ? "" : throwable.getMessage();
        if (!StringUtils.isEmpty(msg)) {
            LOG.error(msg);
        }
        return new TestService() {
            @Override
            public String get(Integer id) {
                return ResultBuilder.unsuccess(ERR_MSG + msg);
            }
        };
    }
}

By implementing FallbackFactory, you can get the exception thrown by the service in the create method. However, please note that the exception here is encapsulated by Feign, and the exception thrown by the original method cannot be seen directly in the exception information. The abnormal information obtained is as follows: status 500 reading TestService#addRecord(ParamVO); content: {"success":false,"resultCode":null,"message":"/ by zero","model":null,"models":[],"pageInfo":null,"timelineInfo":null,"extra":null,"validationMessages":null,"valid":false}

To illustrate, in this example, the interface return information of the service provider will be uniformly encapsulated in the user-defined class Result, and the content is the above content: {"success":false,"resultCode":null,"message":"/ by zero","model":null,"models":[],"pageInfo":null,"timelineInfo":null,"extra":null,"validationMessages":null,"valid":false}

Therefore, I hope the exception message is the content of message: / by zero, so that it is easy to identify the exception when logging.

Keep original exception information

When calling a service, if the status code returned by the service is not 200, it will enter the error decoder of Feign. Therefore, if we want to parse the exception information, we need to rewrite the error decoder:

import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
/**
 * @Author: CipherCui
 * @Description: Keep feign service exception information
 * @Date: Created in 1:29 2018/6/2
 */
public class KeepErrMsgConfiguration {
    @Bean
    public ErrorDecoder errorDecoder() {
        return new UserErrorDecoder();
    }
    /**
     * Custom error
     */
    public class UserErrorDecoder implements ErrorDecoder {
        private Logger logger = LoggerFactory.getLogger(getClass());
        @Override
        public Exception decode(String methodKey, Response response) {
            Exception exception = null;
            try {
                // Get the original returned content
                String json = Util.toString(response.body().asReader());
                exception = new RuntimeException(json);
                // Deserialize the returned content to Result, which should be modified according to its own project
                Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class);
                // The business exception throws a simple RuntimeException to retain the original error information
                if (!result.isSuccess()) {
                    exception = new RuntimeException(result.getMessage());
                }
            } catch (IOException ex) {
                logger.error(ex.getMessage(), ex);
            }
            return exception;
        }
    }
}

The above is an example. The principle is to deserialize the response.body() into a user-defined Result class, extract the message message inside, and then throw a RuntimeException. When entering the fusing method, the exception obtained is the RuntimeException we have handled.

Note that the above examples are not universal, but the principles are interlinked. You should make corresponding modifications in combination with your own projects.

For the above code to work, you also need to specify the configuration in the @ FeignClient annotation:

@FeignClient(name = "serviceId", fallbackFactory = TestServiceFallback.class, configuration = {KeepErrMsgConfiguration.class})
public interface TestService {
    @RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
    String get(@PathVariable("id") Integer id);
    
}

Do not enter the fuse, throw an exception directly

Sometimes we don't want the method to enter the fusing logic, just throw the exception out as it is. In this case, we only need to catch two points: do not enter the fuse, as is.

The original exception is to get the original exception. As mentioned above, instead of entering the fuse, you need to package the exception as a HystrixBadRequestException. For HystrixBadRequestException, Feign will throw it directly instead of entering the fuse method.

Therefore, we only need to make a little modification based on the above KeepErrMsgConfiguration:

/**
 * @Author: CipherCui
 * @Description: feign Abnormal service does not enter the fuse
 * @Date: Created in 1:29 2018/6/2
 */
public class NotBreakerConfiguration {
    @Bean
    public ErrorDecoder errorDecoder() {
        return new UserErrorDecoder();
    }
    /**
     * Custom error
     */
    public class UserErrorDecoder implements ErrorDecoder {
        private Logger logger = LoggerFactory.getLogger(getClass());
        @Override
        public Exception decode(String methodKey, Response response) {
            Exception exception = null;
            try {
                String json = Util.toString(response.body().asReader());
                exception = new RuntimeException(json);
                Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class);
                // The business exception is packaged as HystrixBadRequestException and does not enter the fusing logic
                if (!result.isSuccess()) {
                    exception = new HystrixBadRequestException(result.getMessage());
                }
            } catch (IOException ex) {
                logger.error(ex.getMessage(), ex);
            }
            return exception;
        }
    }
}

summary

In order to achieve better fusing effect, we should specify the fallback method for each interface. According to their own business characteristics, they can flexibly configure the above KeepErrMsgConfiguration and NotBreakerConfiguration, or write their own Configuration.

Reference resources: http://www.ciphermagic.cn/spring-cloud-feign-hystrix.html

Posted by Simbachips on Thu, 16 Jan 2020 07:41:32 -0800