这是本人在项目中总结出来的基础服务异常的处理方式,同时也借鉴了其他博客大神的内容整理出来的

前提

项目中全局禁用了feign的hystirx

feign:
  hystrix:
    enabled: false 

意味着,当基础服务出现异常无法通过feign的fallback配置类降级,这里研究服务熔断的方式在方法

场景所期望的:

1.基础服务能主动抛出自定义异常,同时不触发熔断,把异常信息返给调用者

2.基础服务被动出现异常如空指针,sql无法执行等,触发熔断同时记录异常信息

3.@Valid参数校验逻辑不触发熔断,同样把异常信息返给调用者

问题解决及方案分析

期望1:

在方法上添加@HystrixCommand注解,ignoreExceptions可以指定忽略的异常,比如自定义异常,则不会进入方法的熔断,feign的ErrorDecoder会转换成feignException返回给调用者



期望2: 

指定fallbackMethod的方法,在方法参数上添加Throwable参数,进入熔断逻辑,日志记录异常信息即可


期望3: 

对于@Valid参数校验的异常不会触发服务熔断,因为@HystrixCommand注解是被@Target({ElementType.METHOD})

修饰,只会作用于使用该注解的方法上,同样@Valid的异常会转成feignException,此时的参数校验异常,feign封装成了status=400



为什么400错误也会交给feign的decoder处理,查看feign client对异常处理类的源码SynchronousMethodHandler

Object executeAndDecode(RequestTemplate template) throws Throwable {
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 10
      response.toBuilder().request(request).build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    boolean shouldClose = true;
    try {
      if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
        // ensure the request is set. TODO: remove in Feign 10
        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();
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          return decode(response);
        }
      } else if (decode404 && response.status() == 404) {
        return decoder.decode(response, metadata.returnType());
      } 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());
      }
    }
  }

对于status code处理见这段

  if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          return decode(response);
        }
      } else if (decode404 && response.status() == 404) {
        return decoder.decode(response, metadata.returnType());
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }

都交给了decode处理了,这里重写feign的ErrorDecoder的初衷是借鉴大神的这篇文章,可我这里,请求参数校验并不会触发服务熔断!!

那么,注释掉errorDecoder代码,重新查看参数异常信息



总结: 解决了feign调用微服务的异常处理,更加灵活使用服务熔断

现在的痛点:

  1.feign的ErrorDecoder能获取到status状态,通过feign调用的异常除ignoreExceptions都会经过这里,但是没法获取到具体的异常信息,比如校验参数异常具体是哪个参数不正确的信息,我想能通过ErrorDecoder获取到具体的异常信息从而返回给调用者

2.基于第一点还没有找到实现的方法,考虑从全局异常类捕获FeignException来处理异常信息返回给调用者

具体哪个方法优雅方便,待研究通过之后在另写一篇新文章!!!

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐