Feign调用微服务异常配合Hystrix的正确处理方式
这是本人在项目中总结出来的基础服务异常的处理方式,同时也借鉴了其他博客大神的内容整理出来的前提项目中全局禁用了feign的hystirxfeign:hystrix:enabled: false 意味着,当基础服务出现异常无法通过feign的fallback配置类降级,这里研究服务熔断的方式在方法场景所期望的:1.基础服务能主动抛出自定义异常,同时不触发熔断,把异常信息返给调用者2....
这是本人在项目中总结出来的基础服务异常的处理方式,同时也借鉴了其他博客大神的内容整理出来的
前提
项目中全局禁用了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来处理异常信息返回给调用者
具体哪个方法优雅方便,待研究通过之后在另写一篇新文章!!!
更多推荐
所有评论(0)