探讨通过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;
}
}
WebApplicationException
是 javax.ws.rs
包中异常,通过 Jersey 抛出该异常能够将返回的 HttpCode 封装进该异常中(上述代码中展示了如何封装 HttpCode),抛出该异常,调用端就能得到返回的 HttpCode。
总结
- 本文主要出发点在于如何解决在 Feign 中使用 Hystrix 时被调用端抛出请求异常的问题。
- 本项目使用 Jersey,封装
WebApplicationException
即可满足需求,其他架构也是大同小异了。 - 该解决方案我不确定是否为最佳实践方案,特别希望和欢迎有不同想法或意见的朋友来与我交流,包括但不限于解决方案、项目痛点是否合理等等。
- Nodejs学习笔记(二)--- 事件模块
- 巧用FireFox来调试Silverlight
- Nodejs学习笔记(一)--- 简介及安装Node.js开发环境
- WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成
- 区块链技术(一):Truffle开发入门
- Nodejs学习笔记(一)——初识Nodejs
- RabbitMQ入门-Topic模式
- 单分子数据储存取得一大突破,一枚“硬币”存量相当于100部iPhone 7
- Windows 7 旗舰版 VHD安装体验
- Nodejs学习笔记(二)——Eclipse中运行调试Nodejs
- Nodejs学习笔记(三)——一张图看懂Nodejs建站
- 不规则图形的碰撞检测
- 自学WP7第一个例子:时钟
- 教您最简单粗暴的MATLAB入门级爬虫2
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 面试官问我Linux下常见网络命令
- 最全常用User-Agent
- 聊到JVM(还怕面试官问JVM吗?)
- Android.location.Address类方法获取GPS定位信息
- Python二叉树详解笔记
- 《剑指offer》第七天:二叉树的下一个结点
- 后台登录微信并定时发送消息,消息包括农历、阴历、天气;自动监测是否断线,支持邮箱发送二维码登录;适合于挂在服务器上运行
- 《剑指offer》第八天:二叉树的下一个结点
- 基于python和OpenCV构建智能停车系统
- nvm管理工具
- 基于OpenCV的图像卡通化
- shadertoy绘图
- 单基因生信分析流程(6)单基因相似性分析
- 三阴性乳腺癌提取和分析
- 一日一技:更友好的格式化数据提取方案