前言:
在SpringCloud微服务架构中,服务之间的调用一般通过Fegin完成。Fegin使用方式有点类似于Dubbo接口式调用,使用非常简单便捷。Fegin中集成Hystrix熔断器的功能,可以有效预防服务雪崩的出现。Feign中还集成Ribbon,支持负载均衡,模式轮询方式。在Fegin实现服务之间调用时也会遇到异常的问题,这篇文章,我们就来说一下使用Fegin实现服务之间调用的异常处理。
正文:
首先,我们分析一下那几部分会出现异常:服务生产者的异常,服务消费者的异常,Feign的异常。接下来我们说一说每个部分出现异常的处理思路。
服务生产者的异常:
生产者出现异常在这里有两种处理方式:一是利用全局异常器处理异常,将所有异常都封装到Result类中返回给服务的消费者,消费者根据Result类的信息处理异常;二是生产者不处理异常,将异常上抛给Feign的Hystrix熔断器,让熔断器去处理异常,然后把异常信息反馈给消费者。
方式一:可以全局处理器可以参照《SpringCloud的异常处理体系(一)》。
方式二:这里就需要重写Feign的错误解码器(ErrorDecoder)。原因是Feign会将生产者出现的异常都封装成feign.FeignException类型异常上抛给Hystrix熔断器。这样Hystrix拿不到真正的异常类型,无法做出正确的判断。Feign获取的异常信息举例如下:
{"timestamp":1602770868665,"status":500,"error":"Internal Server Error","exception":"com.hanxiaozhang.exception.InternalServerException","message":"系统繁忙,请稍后重试","path":"/order/1"}
基于以上原因,我们应该重写Feign的错误解码器,我想到的方法是利用反射机制,把Feign异常解析成原本的异常,具体的操作如下:
/**
* 〈一句话功能简述〉<br>
* 〈FeignClient异常解码器〉
* 原理:利用反射机制,把Feign异常解析成原本的异常,
* 不然Hystrix拿到都是feign.FeignException类型异常
*
* @author hanxinghua
* @create 2020/7/30
* @since 1.0.0
*/
@Slf4j
@Configuration
public class FeignClientErrorDecoder implements feign.codec.ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
String className = null;
String message = null;
try {
String errorContent = Util.toString(response.body().asReader());
Map<String, String> map = JsonUtil.jsonToMapSS(errorContent);
if (map == null || map.isEmpty() ||
StringUtil.isBlank(className = map.get("exception"))) {
return new NoFeignDefinitionException();
}
message = map.get("message");
return (Exception) Class.forName(className)
.getConstructor(String.class).newInstance(message);
} catch (Exception e) {
log.error("FeignClientErrorDecoder-decode异常,异常信息:[{}]",e);
return new NoFeignDefinitionException(message==null?e.getMessage():message);
}
}
}
Feign的错误解码器把异常解析成原本的异常后,我们在Hystrix熔断器中就可以使用instanceof关键字判断异常类型,对不同的异常进行封装,返回合适Result类信息的给消费者。
服务消费者的异常:
消费者出现异常,我采取的方法是使用全局异常处理器捕获异常,把异常分装到Result类,返回给用户,具体实现可以参照《SpringCloud的异常处理体系(一)》。这里还有一个小提示,在消费者控制器中(Controller),Result类泛型类可以不指定类型,因为消费者控制器直接面对前端页面,前端接受的是字符串(一般是JSON),没有类型的概念,这样可以减少一些强制转换。举例如下:
public Result<OrderDTO> listByUser(@RequestBody QueryEntity query) { ... }
写成这样:
public Result listByUser(@RequestBody QueryEntity query) { ... }
Feign的异常:
Feign出现异常,也会被Hystrix熔断器处理,这里我们在熔断器中进行判断即可。初次使用可能会遇到“Hystrix首次请求失败超时的异常”,原因是Hystrix默认的超时时间是1s,如果在1s内得不到响应,就进入fallback逻辑。由于Spring使用的懒加载机制,首次请求往往会 比较慢,因此在某些机器(特别是低配的机器)上,首次请求需要的时间可能就会大于1s。我的解决它的方法是在Yaml文件配置延长Hystrix的超时时间,具体配置如下:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000 # 单位ms