最近在项目开发中,使用 Feign 调用服务,当触发熔断机制时,遇到了以下问题:
TestService#addRecord(ParamVO) failed and no fallback available.
;接下来将一一解决上述问题。
对于failed and no fallback available.
这种异常信息,是因为项目开启了熔断:
feign.hystrix.enabled: true
或
@EnableCircuitBreaker
当调用服务时抛出了异常,却没有定义fallback
方法,就会抛出上述异常。由此引出了第一个解决方式。
@FeignClient
加上fallback
方法,并获取异常信息为@FeignClient
修饰的接口加上fallback
方法有两种方式,由于要获取异常信息,所以使用fallbackFactory
的方式:
@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello iFailSometimes();
}
在@FeignClient
注解中指定fallbackFactory
,上面例子中的HystrixClientFallbackFactory
如下:
@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
@Override
public HystrixClient create(Throwable cause) {
return new HystrixClient() {
@Override
public Hello iFailSometimes() {
return new Hello("fallback; reason was: " + cause.getMessage());
}
};
}
}
通过实现FallbackFactory
,可以在create
方法中获取到服务抛出的异常。但是请注意,这里的异常是被Feign
封装过的异常,不能直接在异常信息中看出原始方法抛出的异常。
当调用服务时,如果服务返回的状态码不是200,就会进入到Feign
的ErrorDecoder
中,因此如果我们要解析异常信息,就要重写ErrorDecoder
:
public class KeepErrMsgConfiguration {
@Bean
public ErrorDecoder errorDecoder() {
return new RawErrorDecoder();
}
public class RawErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
String message = null;
try {
if (response.body() != null) {
// 获取原始的返回内容
message = Util.toString(response.body().asReader(Util.UTF_8));
// 将返回内容反序列化为Result,这里应根据自身项目作修改
JSONObject json = JSONObject.parseObject(message);
return new RuntimeException(json.getString("message"));
}
} catch (Exception ignored) {
}
return new RuntimeException(message);
}
}
}
上面是一个例子,原理是根据response.body()
反序列化为自定义的Result
类,提取出里面的message
信息,然后抛出RuntimeException
,这样当进入到熔断方法中时,获取到的异常就是我们处理过的RuntimeException
。
注意上面的例子并不是通用的,但原理是相通的,大家要结合自身的项目作相应的修改。
要使上面代码发挥作用,还需要在@FeignClient
注解中指定configuration
:
@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class, configuration = LogFeignConfiguration.class)
protected interface HystrixClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello iFailSometimes();
}
有时我们并不希望方法进入熔断逻辑,只是把异常原样往外抛。这种情况我们只需要捉住两个点:不进入熔断、原样。
原样就是获取原始的异常,上面已经介绍过了,而不进入熔断,需要把异常封装成HystrixBadRequestException
,对于HystrixBadRequestException
,Feign
会直接抛出,不进入熔断方法。
因此我们只需要在上述KeepErrMsgConfiguration
的基础上作一点修改即可:
public class NotBreakerConfiguration {
@Bean
public ErrorDecoder errorDecoder() {
return new RawErrorDecoder();
}
public class RawErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
String message = null;
try {
if (response.body() != null) {
message = Util.toString(response.body().asReader(Util.UTF_8));
JSONObject json = JSONObject.parseObject(message);
// 业务异常包装成 HystrixBadRequestException,不进入熔断逻辑
return new HystrixBadRequestException(json.getString("message"));
}
} catch (Exception ignored) {
}
return new HystrixBadRequestException(message);
}
}
}
为了更好的达到熔断效果,我们应该为每个接口指定fallback
方法。而根据自身的业务特点,可以灵活的配置上述的KeepErrMsgConfiguration
和NotBreakerConfiguration
,或自己编写Configuration
。
以上例子特殊性较强,不足之处请不吝指教。希望大家可以从中获取到有用的东西,应用到自己的项目中,感谢阅读。
参考文档