基于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