http://www./spring-cloud-feign-hystrix.html
问题最近在项目开发中,使用 Feign 调用服务,当触发熔断机制时,遇到了以下问题:
接下来将一一解决上述问题。 对于
当调用服务时抛出了异常,却没有定义
|
1 2 3 4 5 6 7 | @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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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
封装过的异常,不能直接在异常信息中看出原始方法抛出的异常。这时得到的异常信息形如:
1 2 | 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
:
1 | {"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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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
:
1 2 3 4 5 6 7 | @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
的基础上作一点修改即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | /** * @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./content-4-88501.html
|