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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM