原文連接:www.ciphermagic.cn/spring-clou…html
最近在項目開發中,使用 Feign 調用服務,當觸發熔斷機制時,遇到了如下問題:java
TestService#addRecord(ParamVO) failed and no fallback available.
;接下來將一一解決上述問題。spring
對於failed and no fallback available.
這種異常信息,是由於項目開啓了熔斷:apache
feign.hystrix.enabled: true
複製代碼
當調用服務時拋出了異常,卻沒有定義fallback
方法,就會拋出上述異常。由此引出了第一個解決方式。json
@FeignClient
加上fallback
方法,並獲取異常信息爲@FeignClient
修飾的接口加上fallback
方法有兩種方式,因爲要獲取異常信息,因此使用fallbackFactory
的方式:app
@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
:ide
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
封裝過的異常,不能直接在異常信息中看出原始方法拋出的異常。這時獲得的異常信息形如:ui
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
:spa
{"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
。
以上例子特殊性較強,不足之處請不吝指教。但願你們能夠從中獲取到有用的東西,應用到本身的項目中,感謝閱讀。