zoukankan      html  css  js  c++  java
  • Feign调用报错:failed and no fallback available

    timed-out and no fallback

    这个错误基本是出现在Hystrix熔断器,熔断器的作用是判断该服务能不能通,如果通了就不管了,调用在指定时间内超时时,就会通过熔断器进行错误返回。

    一般设置如下配置的其中一个即可:

    1、把时间设长

    这里设置5秒

    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
    2、把超时发生异常属性关闭

    hystrix.command.default.execution.timeout.enabled=false
    3、禁用feign的hystrix

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

    而通过上面设置只是针对熔断器的错误关闭,并不能解决根本问题,比如Feign客户端调用远程服务时,默认为8秒超时时间,如果在规定时间内没有返回,同样会跳转到熔断器进行处理。即使关闭了熔断器的错误,但是总的错误处理还会是有这个问题出现。

    那么要解决根本问题,就要从请求超时时间入手,因为有些服务可能存在调用时间长的问题,所以直接配置:

    ribbon.ReadTimeout=60000
    ribbon.ConnectTimeout=60000
    这些才是真正解决请求超时的问题,如果不设置这个,被调用接口很慢时,会出现Read Timeout on Request。

    而针对调用失败重试的次数也可以设置:

    ribbon.maxAutoRetries=0

    failed and no fallback available

    对于failed and no fallback available.这种异常信息,是因为项目开启了熔断:
    feign.hystrix.enabled: true

    当调用服务时抛出了异常,却没有定义fallback方法,就会抛出上述异常。由此引出了第一个解决方式。

    @FeignClient加上fallback方法,并获取异常信息

    为@FeignClient修饰的接口加上fallback方法有两种方式,由于要获取异常信息,所以使用fallbackFactory的方式:

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

    在@FeignClient注解中指定fallbackFactory,上面例子中是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接口暂时不可用: ";
        @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);
                }
            };
        }
    }
    

    通过实现FallbackFactory,可以在create方法中获取到服务抛出的异常。但是请注意,这里的异常是被Feign封装过的异常,不能直接在异常信息中看出原始方法抛出的异常。这时得到的异常信息形如:
    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}

    说明一下,本例子中,服务提供者的接口返回信息会统一封装在自定义类Result中,内容就是上述的content:
    {"success":false,"resultCode":null,"message":"/ by zero","model":null,"models":[],"pageInfo":null,"timelineInfo":null,"extra":null,"validationMessages":null,"valid":false}

    因此,异常信息我希望是message的内容:/ by zero,这样打日志时能够方便识别异常。

    保留原始异常信息

    当调用服务时,如果服务返回的状态码不是200,就会进入到Feign的ErrorDecoder中,因此如果我们要解析异常信息,就要重写ErrorDecoder:

    import feign.Response;
    import feign.Util;
    import feign.codec.ErrorDecoder;
    /**
     * @Author: CipherCui
     * @Description: 保留 feign 服务异常信息
     * @Date: Created in 1:29 2018/6/2
     */
    public class KeepErrMsgConfiguration {
        @Bean
        public ErrorDecoder errorDecoder() {
            return new UserErrorDecoder();
        }
        /**
         * 自定义错误
         */
        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 result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class);
                    // 业务异常抛出简单的 RuntimeException,保留原来错误信息
                    if (!result.isSuccess()) {
                        exception = new RuntimeException(result.getMessage());
                    }
                } catch (IOException ex) {
                    logger.error(ex.getMessage(), ex);
                }
                return exception;
            }
        }
    }
    

    上面是一个例子,原理是根据response.body()反序列化为自定义的Result类,提取出里面的message信息,然后抛出RuntimeException,这样当进入到熔断方法中时,获取到的异常就是我们处理过的RuntimeException。

    注意上面的例子并不是通用的,但原理是相通的,大家要结合自身的项目作相应的修改。

    要使上面代码发挥作用,还需要在@FeignClient注解中指定configuration:

    @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);
        
    }
    

    不进入熔断,直接抛出异常

    有时我们并不希望方法进入熔断逻辑,只是把异常原样往外抛。这种情况我们只需要捉住两个点:不进入熔断、原样。

    原样就是获取原始的异常,上面已经介绍过了,而不进入熔断,需要把异常封装成HystrixBadRequestException,对于HystrixBadRequestException,Feign会直接抛出,不进入熔断方法。

    因此我们只需要在上述KeepErrMsgConfiguration的基础上作一点修改即可:

    /**
     * @Author: CipherCui
     * @Description: feign 服务异常不进入熔断
     * @Date: Created in 1:29 2018/6/2
     */
    public class NotBreakerConfiguration {
        @Bean
        public ErrorDecoder errorDecoder() {
            return new UserErrorDecoder();
        }
        /**
         * 自定义错误
         */
        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);
                    // 业务异常包装成 HystrixBadRequestException,不进入熔断逻辑
                    if (!result.isSuccess()) {
                        exception = new HystrixBadRequestException(result.getMessage());
                    }
                } catch (IOException ex) {
                    logger.error(ex.getMessage(), ex);
                }
                return exception;
            }
        }
    }
    

    总结

    为了更好的达到熔断效果,我们应该为每个接口指定fallback方法。而根据自身的业务特点,可以灵活的配置上述的KeepErrMsgConfiguration和NotBreakerConfiguration,或自己编写Configuration。

    参考:http://www.ciphermagic.cn/spring-cloud-feign-hystrix.html

  • 相关阅读:
    中国首届React开发者大会 8月18日 广州举行
    事件循环:Flutter 中代码是如何执行和运行的
    大前端趋势所向:这么多跨端技术,为什么选择 Flutter?
    通往大前端的一把关键钥匙 Flutter
    如何选一部好的手机?性价比高的智能手机推荐,2020智能手机排行榜!
    智能手机边充电边玩对电池有什么损害吗?
    你的智能手机究竟能用多久?
    新型添加技术
    智能手机
    姐姐不愧是姐姐,快看《乘风破浪的姐姐》
  • 原文地址:https://www.cnblogs.com/chen-chen-chen/p/12202480.html
Copyright © 2011-2022 走看看