1. 程式人生 > >Feign呼叫微服務異常配合Hystrix的正確處理方式

Feign呼叫微服務異常配合Hystrix的正確處理方式

這是本人在專案中總結出來的基礎服務異常的處理方式,同時也借鑑了其他部落格大神的內容整理出來的

前提

專案中全域性禁用了feign的hystirx

feign:
  hystrix:
    enabled: false 

意味著,當基礎服務出現異常無法通過feign的fallback配置類降級,這裡研究服務熔斷的方式在方法

場景所期望的:

1.基礎服務能主動丟擲自定義異常,同時不觸發熔斷,把異常資訊返給呼叫者

2.基礎服務被動出現異常如空指標,sql無法執行等,觸發熔斷同時記錄異常資訊

[email protected]引數校驗邏輯不觸發熔斷,同樣把異常資訊返給呼叫者

問題解決及方案分析

期望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來處理異常資訊返回給呼叫者

具體哪個方法優雅方便,待研究通過之後在另寫一篇新文章!!!