这是本人在项目中总结出来的基础服务异常的处理方式,同时也借鉴了其他博客大神的内容整理出来的
前提
项目中全局禁用了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来处理异常信息返回给调用者
具体哪个方法优雅方便,待研究通过之后在另写一篇新文章!!!