我的编程空间,编程开发者的网络收藏夹
学习永远不晚

SpringCloud feign服务熔断下的异常处理操作

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

SpringCloud feign服务熔断下的异常处理操作

今天做项目的时候,遇到一个问题,如果我调用某个服务的接口,但是这个服务挂了,同时业务要求这个接口的结果是必须的,那我该怎么办呢,答案是通过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
{ 
 
 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 {
                    
                    if (e instanceof HystrixBadRequestException) {
                        eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
                        return Observable.error(e);
                    }
 
                    return handleFailureViaFallback(e);
                }
            }
        };

既然如此,那一切都明了了,修改类的继承结构即可:


public class TmallBaseException extends HystrixBadRequestException
{
 
 
 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模块下,所有其它模块都会使用到它。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

SpringCloud feign服务熔断下的异常处理操作

下载Word文档到电脑,方便收藏和打印~

下载Word文档

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录