基于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 && 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 && feignMessage != null && feignMessage.startsWith("status") && feignMessage.contains("reading"); if(readingError) { if(feignE.status() >= 400 && 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