原文连接: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
。
以上例子特殊性较强,不足之处请不吝指教。但愿你们能够从中获取到有用的东西,应用到本身的项目中,感谢阅读。