使用FeignClient进行服务调用时的各类错误列表及快速定位

基于spring cloud 体系的微服务,服务间调用默认使用 openFeign 进行调用,但是由于源码本身的问题。针对于在调用时出现的各类信息,并没有分门别类的错误描述。当业务需要针对不同的错误进行处理时,就不能简单的通过FeignException来进行区分。笔者通过整理服务间调用时的各类异常堆栈及列表。分别列出相应的错误列表及触发时堆栈,以方便后续分别进行业务判断和处理

本应用基于ribbon进行服务查找,使用spring-cloud open-feign, 没有使用retry机制, 底层使用feign-okhttp, 因此,如果有异常与下列堆栈不一致时,请检查是否一致. 

版本: openFeign: 9.7.0, netflix ribbon: 2.2.5,  okhttp3: 3.8.1

总共的异常列表如下所示

  • 无服务时异常
  • 有服务但连接失败
  • 调用中网络错误读取数据超时
  • 响应4xx错误码
  • 响应5xx错误码
  • 解析结果错误(json反序列化失败)
  • 解析结果错误(无converter)

所有的异常触发类均由类 SynchronousMethodHandler 触发,但作用行不一样.
以下异常栈从原始cause,依次往下。如 cause1为 root cause, 最下层为业务层接收到的异常

无服务时异常

cause1: com.netflix.client.ClientException: Load balancer does not have available server for client: 服务名
cause2: java.lang.RuntimeException: com.netflix.client.ClientException: ......

触发行

//Line 98
//没有拦截住, 异常直接throw至业务层
response = client.execute(request, options);

有服务但连接失败

cause1: java.net.SocketTimeoutException: connect timed out
cause2: feign.RetryableException: connect timed out executing GET 访问地址

触发行

//Line 105
//通过try catch 在调用时拦截住异常了,为 IOException
throw errorExecuting(request, e);

调用中网络错误

此种情况一般为调用中或读取数据时,服务端中断连接或出现了网络错误

cause1: java.net.SocketException: Connection reset
cause2: feign.RetryableException: Connection reset executing GET 访问地址

触发行

//Line 105
//通过 try catch 拦截,为IOException
throw errorExecuting(request, e);

读取数据超时

cause1: java.net.SocketTimeoutException: Read timed out
cause2: java.net.SocketTimeoutException: timeout
cause3: feign.RetryableException: timeout executing GET 服务

触发行

//Line 105
//通过 try catch 拦截,为IOException
throw errorExecuting(request, e);

响应4xx错误码

cause1: feign.FeignException: status 状态码(4xx) reading 调用方法 content: 响应内容

触发行

//Line 143
//通过异常解码器默认throw出
errorDecoder.decode(metadata.configKey(), response);

响应5xx错误码

cause1: feign.FeignException: status 状态码(5xx) reading 调用方法; content: 响应内容

触发行

//Line 143
//通过异常解码器默认throw出
errorDecoder.decode(metadata.configKey(), response);

解析结果错误(json反序列化失败)

此情况为响应200时

cause1: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of xxx
cause2: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of xxx
cause3: org.springframework.web.client.RestClientException: Error while extracting response for type xxx
cause4: feign.codec.DecodeException: Error while extracting response for type [类型] and content type [contentType值

触发行

//Line 174
//通过 catch解码 调用 异常时,重新包装异常信息处理
throw new DecodeException(e.getMessage(), e);

解析结果错误(无converter)

cause1: org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found
cause2: feign.codec.DecodeException: Could not extract response: no suitable HttpMessageConverter found for res

触发行

//Line 174
//通过 catch解码 调用 异常时,重新包装异常信息处理
throw new DecodeException(e.getMessage(), e);

总结

从上可知,在几类异常中, 响应异常(4xx和5xx)是一类,响应解析为一类, 服务调用和读取是一类(3种情况), 查找服务为一类。在进行异常分类和定位时,需要按照一定的优先级处理,以最快速的方法即可直接判断出具体的类别.

以下为一个参考的分类代码示意

public static void pickFeignException(Exception e) {
    boolean selfIsFeign = e instanceof FeignException;
    List<Throwable> causeList = ThrowableUtils.getCausalChain(e); //guava 所有cause链
    Throwable rootCause = ThrowableUtils.getRootCause(e, null); //guava root cause

    //无服务
    if(!selfIsFeign &amp;&amp; causeList.stream().anyMatch(t -> t instanceof ClientException)) {
        //无服务异常
        return;
    }

    FeignException feignE = (FeignException) e;
    String feignMessage = feignE.getMessage();

    //响应4xx 5xx异常
    //参考 FeignException#errorStatus 的message 拼接手法
    boolean readingError = feignE.status() != 0 &amp;&amp; feignMessage != null &amp;&amp; feignMessage.startsWith("status") &amp;&amp; feignMessage.contains("reading");
    if(readingError) {
        if(feignE.status() >= 400 &amp;&amp; feignE.status() < 500) {
            //4xx 异常
            return;
        }

        if(feignE.status() >= 500) {
            //5xx 异常
            return;
        }
    }

    //解码异常
    //无converter时 其rootCause即 spring web RestClientException
    if(rootCause instanceof RestClientException) {
        //无converter异常
        return;
    }

    //json反序列化失败
    //其cause链中存在 jackson MismatchedInputException 及 converter HttpMessageNotReadableException 异常
    if(causeList.stream().anyMatch(t -> t instanceof MismatchedInputException || t instanceof HttpMessageNotReadableException)) {
        //反序列化异常
        return;
    }

    //网络失败
    //由于三个错误的堆栈均相一致,仅能通过异常信息来简单的分类处理
    //因此这些异常信息均是由 java 底层网络库throw出,因此可认为这些错误信息不再轻易修改
    if(rootCause instanceof SocketTimeoutException) {
        String message = rootCause.getMessage();
        if(message != null) {
            String lowerMessage = message.toLowerCase();
            if(lowerMessage.contains("connect timed out")) {
                //连接超时异常
                return;
            }
            if(lowerMessage.contains("read timed out")) {
                //读取超时
                return;
            }
        }
    }

    if(rootCause instanceof SocketException) {
        //网络异常
        return;
    }
}

转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/202006010001.html

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

电子邮件地址不会被公开。 必填项已用*标注