Spring Cloud Feign 异常处理

问题

最近在项目开发中,使用 Feign 调用服务,当触发熔断机制时,遇到了以下问题:

  • 异常信息形如:TestService#addRecord(ParamVO) failed and no fallback available.
  • 获取不到服务提供方抛出的原始异常信息;
  • 实现某些业务方法不进入熔断,直接往外抛出异常;

接下来将一一解决上述问题。

对于failed and no fallback available.这种异常信息,是因为项目开启了熔断:

feign.hystrix.enabled: true

当调用服务时抛出了异常,却没有定义fallback方法,就会抛出上述异常。由此引出了第一个解决方式。

@FeignClient加上fallback方法,并获取异常信息

@FeignClient修饰的接口加上fallback方法有两种方式,由于要获取异常信息,所以使用fallbackFactory的方式:

1@FeignClient(name = "serviceId", fallbackFactory = TestServiceFallback.class) 2public interface TestService { 3 @RequestMapping(value = "/get/{id}", method = RequestMethod.GET) 4 Result get(@PathVariable("id") Integer id); 5 6}

@FeignClient注解中指定fallbackFactory,上面例子中是TestServiceFallback

1import feign.hystrix.FallbackFactory; 2import org.apache.commons.lang3.StringUtils; 3@Component 4public class TestServiceFallback implements FallbackFactory<TestService> { 5 private static final Logger LOG = LoggerFactory.getLogger(TestServiceFallback.class); 6 public static final String ERR_MSG = "Test接口暂时不可用: "; 7 @Override 8 public TestService create(Throwable throwable) { 9 String msg = throwable == null ? "" : throwable.getMessage(); 10 if (!StringUtils.isEmpty(msg)) { 11 LOG.error(msg); 12 } 13 return new TestService() { 14 @Override 15 public String get(Integer id) { 16 return ResultBuilder.unsuccess(ERR_MSG + msg); 17 } 18 }; 19 } 20}

通过实现FallbackFactory,可以在create方法中获取到服务抛出的异常。但是请注意,这里的异常是被Feign封装过的异常,不能直接在异常信息中看出原始方法抛出的异常。这时得到的异常信息形如:

1status 500 reading TestService#addRecord(ParamVO); content: 2{"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,就会进入到FeignErrorDecoder中,因此如果我们要解析异常信息,就要重写ErrorDecoder

1import feign.Response; 2import feign.Util; 3import feign.codec.ErrorDecoder; 4/** 5 * @Author: CipherCui 6 * @Description: 保留 feign 服务异常信息 7 * @Date: Created in 1:29 2018/6/2 8 */ 9public class KeepErrMsgConfiguration { 10 @Bean 11 public ErrorDecoder errorDecoder() { 12 return new UserErrorDecoder(); 13 } 14 /** 15 * 自定义错误 16 */ 17 public class UserErrorDecoder implements ErrorDecoder { 18 private Logger logger = LoggerFactory.getLogger(getClass()); 19 @Override 20 public Exception decode(String methodKey, Response response) { 21 Exception exception = null; 22 try { 23 // 获取原始的返回内容 24 String json = Util.toString(response.body().asReader()); 25 exception = new RuntimeException(json); 26 // 将返回内容反序列化为Result,这里应根据自身项目作修改 27 Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class); 28 // 业务异常抛出简单的 RuntimeException,保留原来错误信息 29 if (!result.isSuccess()) { 30 exception = new RuntimeException(result.getMessage()); 31 } 32 } catch (IOException ex) { 33 logger.error(ex.getMessage(), ex); 34 } 35 return exception; 36 } 37 } 38}

上面是一个例子,原理是根据response.body()反序列化为自定义的Result类,提取出里面的message信息,然后抛出RuntimeException,这样当进入到熔断方法中时,获取到的异常就是我们处理过的RuntimeException

注意上面的例子并不是通用的,但原理是相通的,大家要结合自身的项目作相应的修改。

要使上面代码发挥作用,还需要在@FeignClient注解中指定configuration

1@FeignClient(name = "serviceId", fallbackFactory = TestServiceFallback.class, configuration = {KeepErrMsgConfiguration.class}) 2public interface TestService { 3 @RequestMapping(value = "/get/{id}", method = RequestMethod.GET) 4 String get(@PathVariable("id") Integer id); 5 6}

不进入熔断,直接抛出异常

有时我们并不希望方法进入熔断逻辑,只是把异常原样往外抛。这种情况我们只需要捉住两个点:不进入熔断原样

原样就是获取原始的异常,上面已经介绍过了,而不进入熔断,需要把异常封装成HystrixBadRequestException,对于HystrixBadRequestExceptionFeign会直接抛出,不进入熔断方法。

因此我们只需要在上述KeepErrMsgConfiguration的基础上作一点修改即可:

1/** 2 * @Author: CipherCui 3 * @Description: feign 服务异常不进入熔断 4 * @Date: Created in 1:29 2018/6/2 5 */ 6public class NotBreakerConfiguration { 7 @Bean 8 public ErrorDecoder errorDecoder() { 9 return new UserErrorDecoder(); 10 } 11 /** 12 * 自定义错误 13 */ 14 public class UserErrorDecoder implements ErrorDecoder { 15 private Logger logger = LoggerFactory.getLogger(getClass()); 16 @Override 17 public Exception decode(String methodKey, Response response) { 18 Exception exception = null; 19 try { 20 String json = Util.toString(response.body().asReader()); 21 exception = new RuntimeException(json); 22 Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class); 23 // 业务异常包装成 HystrixBadRequestException,不进入熔断逻辑 24 if (!result.isSuccess()) { 25 exception = new HystrixBadRequestException(result.getMessage()); 26 } 27 } catch (IOException ex) { 28 logger.error(ex.getMessage(), ex); 29 } 30 return exception; 31 } 32 } 33}

总结

为了更好的达到熔断效果,我们应该为每个接口指定fallback方法。而根据自身的业务特点,可以灵活的配置上述的KeepErrMsgConfigurationNotBreakerConfiguration,或自己编写Configuration

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Dubbo的异常处理

最近在整Dubbo的框架,使用zookeeper做注册中心,项目中有service层作为provider(提供者),web层作为consumer(消费者),当在做自定义异常的抛出时,遇到了问题:1、web层不能识别异常为自定义异常类型,异常类型为RuntimeException。2、当修改识别类型为RuntimeException时,从异常当中获取到

SpringMVC源码(五)

SpringMVC除了对请求URL的路由处理特别方便外,还支持对异常的统一处理机制,可以对业务操作时抛出的异常,unchecked异常以及状态码的异常进行统一处理。SpringMVC既提供简单的配置类,也提供了细粒度的异常控制机制。SpringMVC中所有的异常处理通过接口HandlerExceptionResolver来实现,接口中只定义了一个方法

03.Android崩溃Crash库之ExceptionHandler分析

目录总结00.异常处理几个常用api01.UncaughtExceptionHandler02.Java线程处理异常分析03.Android中线程处理异常分析04.为何使用setDefaultUncaughtExceptionHandler前沿上一篇整体介绍了crash崩溃