探讨通过Feign配合Hystrix进行调用时异常的处理

作者:任聪

原文:http://www.jianshu.com/p/f240ca7bb7c0

前言:此文所述处理方式为本人在实践过程中研究分析得出的一种解决方案。

本文不仅希望能为 SC 学习者提供一种如题问题的一种解决方案,并且希望通过本文引出各位 SC 的朋友对如题问题的共同探讨和最佳实践方案的分享。

场景及痛点

  • 单个项目是通过 Jersey 来实现 restful 风格的架构
  • 发生异常时异常信息总是提示没有回调方法,不能显示基础服务抛出的异常信息
  • 暂时没有考虑发生异常之后进行回调返回特定内容
  • 业务系统通过 feign 调用基础服务,基础服务是会根据请求抛出各种请求异常的(采用标准http状态码),现在我的想法是如果调用基础服务时发生请求异常,业务系统返回的能够返回基础服务抛出的状态码
  • 当然基础服务抛出的请求异常不能触发 hystrix 的熔断机制

问题分析与解决方案

解决思路

  • 通过网上一些资料的查询,看到很多文章会说 HystrixBadRequestException 不会触发 hystrix 的熔断 --> 但是并没有介绍该异常的实践方案
  • 感觉要解决项目的痛点,切入点应该就在 HystrixBadRequestException 了。于是先看源码,一方面对 Hystrix 加深理解,尝试理解作者设计的初衷与想法,另一方面看看是否能找到其他方案达到较高的实践标准

主要类对象简介

  • interface UserRemoteCall 定义feign的接口其上会有 @FeignClient,FeignClient 定义了自己的 Configuration --> FeignConfiguration
  • class FeignConfiguration 这里是定义了指定 Feign 接口使用的自定义配置,如果不想该配置成为全局配置,不要让该类被自动扫描到
  • class UserErrorDecoder implements ErrorDecoder 该类会处理响应状态码 (![200,300) || !404)

源码分析

Feign 的默认配置在 org.springframework.cloud.netflix.feign.FeignClientsConfiguration类中,如果不自定义Feign.Builder,会优先配置 feign.hystrix.HystrixFeign.Builder extends Feign.Builder,该类会让 Feign 的内部调用受到 Hystrix 的控制

//省略部分代码@Configuration@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {        
   @Bean   
   @Scope("prototype")       
   @ConditionalOnMissingBean
   @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
   public Feign.Builder feignHystrixBuilder() {           
        return HystrixFeign.builder();
  }
}//省略部分代码

解决方案

  • 当然不使用 Hystrix 就不会有熔断等问题出现,处理好 ErrorDecoder.decode() 即可。
  • 不开启 Hystrix 的方式:
  • 配置增加 feign.hystrix.enabled=false ,这会在全局生效不推荐。
  • FeignConfiguration 增加:(推荐) @Bean @Scope("prototype") public Feign.Builder feignBuilder() { return Feign.builder(); }

源码分析

Hystrix 的设计方案是通过命令模式加 RxJava 实现的观察者模式来开发的,想完全熟悉 Hystrix 的运作流程需要熟练掌握 RxJava,本文只对源码进行简单介绍,后面有时间有机会再详细介绍。Hystrix如何处理异常的代码位置:com.netflix.hystrix.AbstractCommand#executeCommandAndObserve

//省略部分代码private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {//省略部分代码       final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {        @Override
       public Observable<R> call(Throwable t) {      
         Exception e = getExceptionFromThrowable(t);       
          executionResult = executionResult.setExecutionException(e);      
            if (e instanceof RejectedExecutionException) {                       
                       return handleThreadPoolRejectionViaFallback(e);
        } else if (t instanceof HystrixTimeoutException) {                      
                    return handleTimeoutViaFallback();
        } else if (t instanceof HystrixBadRequestException) {                        
                  return handleBadRequestByEmittingError(e);
        } else {                  
            if (e instanceof HystrixBadRequestException) {
               eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);                                          return Observable.error(e);
           }                          
                   return handleFailureViaFallback(e);
        }
       }
    };//省略部分代码}

该类中该方法为发生异常的回调方法,由此可以看出如果抛出异常如果是 HystrixBadRequestException 是直接处理异常之后进行抛出(这里不会触发熔断机制),而不是进入回调方法。

解决方案

那么我们对于请求异常的解决方案就需要通过 HystrixBadRequestException 来解决了(不会触发熔断机制),根据返回响应创建对应异常并将异常封装进 HystrixBadRequestException,业务系统调用中取出 HystrixBadRequestException 中的自定义异常进行处理,封装异常说明:

public class UserErrorDecoder implements ErrorDecoder{    
    private Logger logger = LoggerFactory.getLogger(getClass());     
       public Exception decode(String methodKey, Response response) {
        ObjectMapper om = new JiaJianJacksonObjectMapper();
        JiaJianResponse resEntity;          
        Exception exception = null;         
           try {
                 resEntity = om.readValue(Util.toString(response.body().asReader()), JiaJianResponse.class);//为了说明我使用的 WebApplicationException 基类,去掉了封装
                 exception = new WebApplicationException(javax.ws.rs.core.Response.status(response.status()).entity(resEntity).type(MediaType.APPLICATION_JSON).build());
               } catch (IOException ex) {
                  logger.error(ex.getMessage(), ex);
                  }        // 这里只封装4开头的请求异常
                      if (400 <= response.status() || response.status() < 500){   
                               exception = new HystrixBadRequestException("request exception wrapper", exception);
                      }else{
                         logger.error(exception.getMessage(), exception);
      }                return exception;
   }
}

为 Feign 配置 ErrorDecoder

@Configurationpublic class FeignConfiguration {     
   @Bean
   public ErrorDecoder errorDecoder(){        
           return new UserErrorDecoder();
   }
}

业务系统处理异常说明:

@Overridepublic UserSigninResEntity signIn(UserSigninReqEntity param) throws Exception {  
      try {//省略部分代码
        UserSigninResEntity entity = userRemoteCall.signin(secretConfiguration.getKeys().get("user-service"), param);//省略部分代码
       } catch (Exception ex) {
        logger.error(ex.getMessage(), ex);//这里进行异常处理
       if(ex.getCause() instanceof WebApplicationException){       
                        throw (WebApplicationException) ex.getCause();
       }                   
        throw ex;
    }
}

WebApplicationExceptionjavax.ws.rs 包中异常,通过 Jersey 抛出该异常能够将返回的 HttpCode 封装进该异常中(上述代码中展示了如何封装 HttpCode),抛出该异常,调用端就能得到返回的 HttpCode。

总结

  • 本文主要出发点在于如何解决在 Feign 中使用 Hystrix 时被调用端抛出请求异常的问题。
  • 本项目使用 Jersey,封装 WebApplicationException 即可满足需求,其他架构也是大同小异了。
  • 该解决方案我不确定是否为最佳实践方案,特别希望和欢迎有不同想法或意见的朋友来与我交流,包括但不限于解决方案、项目痛点是否合理等等。

原文发布于微信公众号 - 程序猿DD(didispace)

原文发表时间:2017-06-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏企鹅号快讯

Java 9 逆天的十大新特性

在介绍 Java 9 之前,我们先来看看 Java 成立到现在的所有版本。 1990 年初,最初被命名为 Oak; 1995 年 5 月 23 日,Java 语...

2285
来自专栏精讲JAVA

详述 PO VO BO DTO DAO 和 POJO 的概念及区别

 说实话,我相信对于刚接触 PO、VO、BO、DTO、DAO 和 POJO 这些概念的同学来说,大都会有一种“这都是什么鬼?”的感觉,可谓是云里雾里,不知今...

1615
来自专栏Hongten

JSP 六讲

1322
来自专栏机器学习从入门到成神

Java 进阶面试问题列表

1041
来自专栏Java开发者杂谈

分布式改造剧集之Redis缓存踩坑记

2464
来自专栏HansBug's Lab

【技巧】Java工程中的Debug信息分级输出接口及部署模式

UPDATE: 2018.4.4 笔者将考虑将这一模块封装成一个完整的java第三方包并可能进行开源放送,完成后将会再次发布最新消息,敬请期待。 -------...

4156
来自专栏技术墨客

Spring核心——设计模式与IoC 原

“Spring”——每一个Javaer开发者都绕不开的字眼,从21世纪第一个十年国内异常活跃的SSH框架,到现在以Spring Boot作为入口粘合了各种应用。...

3251
来自专栏架构师之旅

【强烈推荐】Java工程师如何从一名普通的码农成长为一位大神

本文源自 http://www.hollischuang.com/archives/489 写在前面 java作为一门编程语言,在各类编程语言中...

2638
来自专栏ShaoYL

XCode调试器LLDB

1526
来自专栏维C果糖

详述 PO VO BO DTO DAO 和 POJO 的概念及区别

  说实话,我相信对于刚接触 PO、VO、BO、DTO、DAO 和 POJO 这些概念的同学来说,大都会有一种“这都是什么鬼?”的感觉,可谓是云里雾里,不知今夕...

2845

扫码关注云+社区

领取腾讯云代金券