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

2019-01-18 10:21 在此推荐下自个的一个基础复习库,囊括常见的数据结构和算法以及一些解决方案,还处于更新状态

---------------------------------------------------------------------------------------------------------------------------------------

 

今天做项目的时候,遇到一个问题,如果我调用某个服务的接口,但是这个服务挂了,同时业务要求这个接口的结果是必须的,那我该怎么办呢,答案是通过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模块下,所有其他模块都会使用到他

参考资料:程序猿DD大佬

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Spring Cloud Alibaba提供了一个名为Sentinel的熔断降级框架,可以方便地与Spring Cloud集成。下面是使用Spring Cloud Alibaba Sentinel进行熔断降级的步骤: 1. 添加依赖 在`pom.xml`文件中添加以下依赖: ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.3.RELEASE</version> </dependency> ``` 2. 配置Sentinel 在`application.yml`文件中添加以下配置: ```yaml spring: cloud: sentinel: transport: dashboard: localhost:8080 #Sentinel Dashboard地址 port: 8719 #Sentinel客户端监听端口号 ## 开启Sentinel熔断降级 feign: sentinel: enabled: true ``` 3. 配置熔断降级规则 在`resources`目录下创建一个名为`META-INF`的文件夹,然后在该文件夹下创建一个名为`flowrule`的文件,文件名可以自己定义。在该文件中添加以下内容,这里以配置一个`Hello World`的熔断降级规则为例: ```json [ { "resource": "hello", //资源名称,可以是方法名或URL "count": 5, //阈值 "grade": 0, //熔断策略,0表示平均响应时间,1表示异常比率 "timeWindow": 5, //时间窗口,单位是秒 "minRequestAmount": 5, //最小请求数量 "slowRatioThreshold": 0 //慢调用比例阈值 } ] ``` 4. 使用@SentinelResource注解 在需要进行熔断降级的方法上添加`@SentinelResource`注解,并指定资源名称,例如: ```java @SentinelResource(value = "hello", fallback = "fallback") public String hello() { return "Hello World"; } public String fallback() { return "fallback"; } ``` 其中`fallback`方法为熔断降级时的备选方法。 5. 启动Sentinel Dashboard 在命令行中输入以下命令启动Sentinel Dashboard: ```bash java -jar sentinel-dashboard-1.8.2.jar ``` 访问`http://localhost:8080`即可进入Sentinel Dashboard。 6. 启动应用程序 启动应用程序后,可以在Sentinel Dashboard中看到应用程序的熔断降级情况。当资源的请求次数超过阈值时,Sentinel将自动触发熔断降级策略,调用`fallback`方法,返回备选结果。 以上就是使用Spring Cloud Alibaba Sentinel进行熔断降级的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值