gpt4 book ai didi

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

转载 作者:qq735679552 更新时间:2022-09-29 22:32:09 26 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章SpringCloud feign服务熔断下的异常处理操作由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

今天做项目的时候,遇到一个问题,如果我调用某个服务的接口,但是这个服务挂了,同时业务要求这个接口的结果是必须的,那我该怎么办呢,答案是通过hystrix,但是又有一点,服务不是平白无故挂的(排除服务器停电等问题),也就是说有可能是timeout or wrong argument 等等,那么我该如何越过hystrix的同时又能将异常成功抛出呢 。

第一点:先总结一下异常处理的方式:

1):通过在controller中编写@ExceptionHandler 方法

直接在controller中编写异常处理器方法 。

?
1
2
3
4
5
6
7
8
9
10
@RequestMapping ( "/test" )
public ModelAndView test()
{
  throw new TmallBaseException();
}
@ExceptionHandler (TmallBaseException. class )
public ModelAndView handleBaseException()
{
  return new ModelAndView( "error" );
}

但是呢这种方法只能在这个controller中有效,如果其他的controller也抛出了这个异常,是不会执行的 。

2):全局异常处理:

?
1
2
3
4
5
6
7
8
9
10
@ControllerAdvice
public class AdminExceptionHandler
{
  @ExceptionHandler (TmallBaseException. class )
  public ModelAndView hAndView(Exception exception)
  {
   //logic
   return null ;
  }
}

本质是aop代理,如名字所言,全局异常处理,可以处理任意方法抛出的异常 。

3)通过实现SpringMVC的HandlerExceptionResolver接口

?
1
2
3
4
5
6
7
8
9
10
public static class Tt implements HandlerExceptionResolver
  {
   @Override
   public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
    Exception ex)
   {
    //logic
    return null ;
  
  }

然后在mvc配置中添加即可 。

?
1
2
3
4
5
6
7
8
@Configuration
public class MyConfiguration extends WebMvcConfigurerAdapter { 
     @Override
     public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
     //初始化异常处理器链
         exceptionResolvers.add( new Tt());
     }
}

接下来就是Fegin ,如果想自定义异常需要了解1个接口:ErrorDecoder 。

先来看下rmi调用结束后是如果进行decode的 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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());
       }
     }
   }

默认的解析方式是:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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:

?
1
2
3
4
5
6
7
//显示某个商家合作的店铺
  @RequestMapping (value= "/store" )
  public ResultDTO<Collection<BrandDTO>>findStoreOperatedBrands( @RequestParam ( "storeId" )Long storeId)
  {
         为了测试,先直接抛出异常
   throw new TmallBaseException(TmallBaseException.BIZ_EXCEPTION, "ceshi" );
     }

接口:

?
1
2
@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,也就意味着不会触发降级 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
                 }
             }
         };

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 。

最后此篇关于SpringCloud feign服务熔断下的异常处理操作的文章就讲到这里了,如果你想了解更多关于SpringCloud feign服务熔断下的异常处理操作的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com