SpringCloud feign服务熔断下的异常处理操作
作者:Coder_Joker 发布时间:2022-01-11 20:22:45
今天做项目的时候,遇到一个问题,如果我调用某个服务的接口,但是这个服务挂了,同时业务要求这个接口的结果是必须的,那我该怎么办呢,答案是通过hystrix,但是又有一点,服务不是平白无故挂的(排除服务器停电等问题),也就是说有可能是timeout or wrong argument 等等,那么我该如何越过hystrix的同时又能将异常成功抛出呢
第一点:先总结一下异常处理的方式:
1):通过在controller中编写@ExceptionHandler 方法
直接在controller中编写异常处理器方法
@RequestMapping("/test")
public ModelAndView test()
{
throw new TmallBaseException();
}
@ExceptionHandler(TmallBaseException.class)
public ModelAndView handleBaseException()
{
return new ModelAndView("error");
}
但是呢这种方法只能在这个controller中有效,如果其他的controller也抛出了这个异常,是不会执行的
2):全局异常处理:
@ControllerAdvice
public class AdminExceptionHandler
{
@ExceptionHandler(TmallBaseException.class)
public ModelAndView hAndView(Exception exception)
{
//logic
return null;
}
}
本质是aop代理,如名字所言,全局异常处理,可以处理任意方法抛出的异常
3)通过实现SpringMVC的HandlerExceptionResolver接口
public static class Tt implements HandlerExceptionResolver
{
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex)
{
//logic
return null;
}
}
然后在mvc配置中添加即可
@Configuration
public class MyConfiguration extends WebMvcConfigurerAdapter {
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
//初始化异常处理器链
exceptionResolvers.add(new Tt());
}
}
接下来就是Fegin ,如果想自定义异常需要了解1个接口:ErrorDecoder
先来看下rmi调用结束后是如果进行decode的
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
//代码省略
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
response.toBuilder().request(request).build();
}
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
//从此处可以发现,如果状态码不再200-300,或是404的时候,意味着非正常响应就会对内部异常进行解析
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
return decode(response);
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
return decode(response);
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
默认的解析方式是:
public static class Default implements ErrorDecoder {
private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();
@Override
public Exception decode(String methodKey, Response response) {
//获取错误状态码,生成fegin自定义的exception
FeignException exception = errorStatus(methodKey, response);
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null) {
//如果重试多次失败,则抛出相应的exception
return new RetryableException(exception.getMessage(), exception, retryAfter);
}
//否则抛出默认的exception
return exception;
}
我们可以发现,做了2件事,第一获取状态码,第二重新抛出异常,额外的判断是否存在多次失败依然error的异常,并没有封装太多的异常,既然如此那我们就可以封装我们自定义的异常了
但是注意,这块并没有涉及hystrix,也就意味着对异常进行处理还是会触发熔断机制,具体避免方法最后讲
首先我们编写一个BaseException 用于扩展:省略getter/setter
public class TmallBaseException extends RuntimeException
{
/**
*
* @author joker
* @date 创建时间:2018年8月18日 下午4:46:54
*/
private static final long serialVersionUID = -5076254306303975358L;
// 未认证
public static final int UNAUTHENTICATED_EXCEPTION = 0;
// 未授权
public static final int FORBIDDEN_EXCEPTION = 1;
// 超时
public static final int TIMEOUT_EXCEPTION = 2;
// 业务逻辑异常
public static final int BIZ_EXCEPTION = 3;
// 未知异常->系统异常
public static final int UNKNOWN_EXCEPTION = 4;
// 异常码
private int code;
// 异常信息
private String message;
public TmallBaseException(int code, String message)
{
super(message);
this.code = code;
this.message = message;
}
public TmallBaseException(String message, Throwable cause)
{
super(message, cause);
this.message = message;
}
public TmallBaseException(int code, String message, Throwable cause)
{
super(message, cause);
this.code = code;
this.message = message;
}
}
OK,我们定义好了基类之后可以先进行测试一番:服务接口controller:
//显示某个商家合作的店铺
@RequestMapping(value="/store")
public ResultDTO<Collection<BrandDTO>>findStoreOperatedBrands(@RequestParam("storeId")Long storeId)
{
为了测试,先直接抛出异常
throw new TmallBaseException(TmallBaseException.BIZ_EXCEPTION, "ceshi");
}
接口:
@RequestMapping(value="/auth/brand/store",method=RequestMethod.POST,produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
ResultDTO<List<BrandDTO>>findStoreOperatedBrands(@RequestParam("storeId")Long storeId);
其余的先不贴了,然后我们发起rest调用的时候发现,抛出异常之后并没有被异常处理器处理,这是因为我们是通过fegin,而我又配置了feign的fallback类,抛出异常的时候会自动调用这个类中的方法.
有两种解决方法:
1.直接撤除hystrix ,很明显its not a good idea
2.再封装一层异常类,具体为何,如下
AbstractCommand#handleFallback 函数是处理异常的函数,从方法后缀名可以得知,当exception 是HystrixBadRequestException的时候是直接抛出的,不会触发fallback,也就意味着不会触发降级
final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
@Override
public Observable<R> call(Throwable t) {
circuitBreaker.markNonSuccess();
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 {
/*
* Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException.
*/
if (e instanceof HystrixBadRequestException) {
eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
return Observable.error(e);
}
return handleFailureViaFallback(e);
}
}
};
既然如此,那一切都明了了,修改类的继承结构即可:
public class TmallBaseException extends HystrixBadRequestException
{
/**
*
* @author joker
* @date 创建时间:2018年8月18日 下午4:46:54
*/
private static final long serialVersionUID = -5076254306303975358L;
// 未认证
public static final int UNAUTHENTICATED_EXCEPTION = 0;
// 未授权
public static final int FORBIDDEN_EXCEPTION = 1;
// 超时
public static final int TIMEOUT_EXCEPTION = 2;
// 业务逻辑异常
public static final int BIZ_EXCEPTION = 3;
// 未知异常->系统异常
public static final int UNKNOWN_EXCEPTION = 4;
// 异常码
private int code;
// 异常信息
private String message;
}
至于怎么从服务器中获取异常然后进行转换,就是通过上面所讲的ErrorHandler:
public class TmallErrorDecoder implements ErrorDecoder
{
@Override
public Exception decode(String methodKey, Response response)
{
System.out.println(methodKey);
Exception exception=null;
try
{
String json = Util.toString(response.body().asReader());
exception=JsonUtils.json2Object(json,TmallBaseException.class);
} catch (IOException e)
{
e.printStackTrace();
}
return exception!=null?exception:new TmallBaseException(TmallBaseException.UNKNOWN_EXCEPTION, "系统运行异常");
}
}
最后微服务下的全局异常处理就ok了,当然这个ErrorDdecoder 和BaseException推荐放在common模块下,所有其它模块都会使用到它。
来源:https://blog.csdn.net/Coder_Joker/article/details/81811567
猜你喜欢
- 前言分页是我们在开发中绕不过去的一个坎!当你的数据量大了的时候,一次性将所有数据查出来不现实,所以我们一般都是分页查询的,减轻服务端的压力,
- WPF 实现筛选下拉多选控件框架使用.NET4 至 .NET6;Visual Studio 2022;创建 MultiSelect
- 项目结构:运行效果:========================================================下面是代
- 题目描述Java创建线程的几种方式Java使用Thread类代表线程,所有线程对象都必须是Thread类或者其子类的实例。Java可以用以下
- 前台form 表单:设置method=post,enctype=multipart/form-data。struts2在原有的上传解析器继承
- 背景:在Android中按照数据保存的方式,可以分为如下几种Content Provider (用的SQLite实现),SQLite,Sha
- 本文实例为大家分享了Java实现猜数字游戏的具体代码,供大家参考,具体内容如下完成猜数字游戏需要实现以下几点:获得一个随机数作为“答案数”;
- 一、设置Jackson序列化时只包含不为空的字段new ObjectMapper().setSerializationInclusion(I
- 一、前言文稿扫描大家用的都比较频繁、想是各种证件、文件都可以通过扫描文稿功能保存到手机。相比直接拍照,在扫描文稿时,程序会对图像进行一些矫正
- 目录首先,写一个需求文档:一、登录界面1.界面2.登录3.退出二、开始游戏界面三、缓冲加载游戏界面四、游戏主界面五、结束界面上代码首先,写一
- 一、redis key数量为1千万时。存储value为"0",比较小。如果value较大,则存储内存会增多redis k
- 1. 什么是对象池对象池,顾名思义就是一定数量的已经创建好的对象(Object)的集合。当需要创建对象时,先在池子中获取,如果池子中没有符合
- 问题(1)条件锁是什么?(2)条件锁适用于什么场景?(3)条件锁的await()是在其它线程signal()的时候唤醒的吗?简介条件锁,是指
- 目录1、如果一个方法或变量是"private"访问级别,那么它的访问范围是:2、代码将打印?3、下面关于hibernat
- 前言今天的文章从下面这张图片开始,这张图片Java开发们应该很熟悉了我们都知道无锁状态是对象头是有位置存储hashcode的,而变为偏向锁状
- 摘要:用spring-boot开发RESTful API非常的方便,在生产环境中,对发布的API增加授权保护是非常必要的。现在我们来看如何利
- 在网站开发中经常遇到级联数据的展示,比如选择城市的时候弹出的省市县选择界面。很多前端制作人员习惯于从JSON中而不是从数据库中获
- 一、位运算的分类与展现效果java位运算可以分为左移和右移,其中右移还有无符号右移。 java只对整型位移,可以分为int体系和long体系
- 简介说明本文用实例介绍stream的使用。JDK8新增了Stream(流操作) 处理集合的数据,可执行查找、过滤和映射数据等操作。使用Str
- 在Java中,我们可以对List集合进行如下几种方式的遍历:List<Integer> list = new ArrayList