如何自定义feign调用实现hystrix超时、异常熔断
需求描述
spring cloud 项目中feign 整合 hystrix经常使用,但是最近发现hystrix功能强大,但是对我们来说有些大材小用。
首先我只需要他的一个熔断作用,就是说请求超时、异常了返回 FeignClient注解中配置的fallback,不需要非阻塞操作、也不需要重试,hystrix 调用feign时候做了线程池隔离处理,这样增加了项目复杂度(线程池参数配置、线程少了请求服务直接拒绝,多了线程得管理。。。)
目前feign 超时之后是直接抛异常的,这样的话虽然是及时熔断了,但是正常的程序逻辑不走了配置的fallback也没有作用,这个配置项得配合 hystrix 才行。
我需要的是这样的效果
try{
feign.api();
}catch(){
return fallback();
}
但是每个feign调用都手动加上try..catch 实在是太low了,最好能写个类似切面一样的玩意。
这时候就想到了 hystrix,既然人家框架已经做了,我直接看下代码,copy不完了么
源码学习
前两天发布了一篇文章也是关于feign、hystrix 调用集成的
基于之前的分析关键代码
HystrixInvocationHandler (feign.hystrix)
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
.............
// setterMethodMap 封装 hystrixCommand 配置信息(超时时间、是否重试.....)
HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
....
HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
....
}
@Override
protected Object getFallback() {
.........
}
};
......
return hystrixCommand.execute();
}
按照之前分析源码方式,直接看哪里被调用了就可以看到, hystrix 实际上自己封装了一个 feign.Builer 类名是 feign.hystrix.HystrixFeign.Builder 用的是建造者模式,生成的类是在调用服务时用到
看到 关键的 build() 方法
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
// 重新定义一个 InvocationHandler 实现 类似 aop效果
@Override public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
}
});
super.contract(new HystrixDelegatingContract(contract));
return super.build();
}
spring 动态代理我这里不多说了,核心就是 InvocationHandler (如果是jdk动态代理的话),那么 feign 这里也是,我们看看feign 调用声明是个接口,实际上是spring 动态代理生成了代理类,调用方法时实际调用的是
java.lang.reflect.InvocationHandler#invoke
方案构想
那么我们只需要借鉴下 hystrix 的方式,自己实现一个feign.build ,将 InvocationHandler 换成自己的,
然后在我们自己的 InvocationHandler 中调用feign 官方的 InvocationHandler 就行,也就是
feign.hystrix.HystrixInvocationHandler#invoke
这个方法中的
this.dispatch.get(method).invoke(args);
这个代码
方案具体代码实现
方案一
自己实现
import feign.Feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
public class CusFeignBuilder extends Feign.Builder{
public CusFeignBuilder() {
this.invocationHandlerFactory((target, dispatch) -> {
Class<?> type = target.type();
FeignClient annotation = type.getAnnotation(FeignClient.class);
// 构造 fallback 实例
Object fallBackObj = null;
if (annotation != null && !annotation.fallback().equals(void.class)) {
try {
fallBackObj = annotation.fallback().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
return new CusFeignInvocationHandler(target, dispatch, fallBackObj);
});
}
}
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignClient;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static com.eco.common.utils.Md5Util.logger;
import static feign.Util.checkNotNull;
@Slf4j
public class CusFeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private final Object fallbackObj;
private final Map<String, Method> fallbackMethodMap = new ConcurrentHashMap<>();
CusFeignInvocationHandler(Target target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, Object fallbackObj) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
this.fallbackObj = fallbackObj;
}
public Object feignInvoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object
otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
return dispatch.get(method).invoke(args);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CusFeignInvocationHandler) {
CusFeignInvocationHandler other = (CusFeignInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return feignInvoke(proxy, method, args);
} catch (Throwable throwable) {
String configKey = Feign.configKey(target.type(), method);
logger.error("{} 请求 出现异常 ==> {}", configKey, throwable.getMessage());
try {
return getFallbackReturn(method, args, throwable);
} catch (Throwable e) {
throw throwable;
}
}
}
public Object getFallbackReturn(Method method, Object[] args, Throwable throwable) throws Throwable {
if (fallbackObj == null) {
throw new RuntimeException("fallbackObj is null");
}
String configKey = Feign.configKey(target.type(), method);
Method fallbackMethod = fallbackMethodMap.get(configKey);
if (fallbackMethod == null) {
Class<?> declaringClass = method.getDeclaringClass();
FeignClient annotation = declaringClass.getAnnotation(FeignClient.class);
if (annotation == null) {
throw new RuntimeException("FeignClient annotation not found");
}
// 失败返回
Class<?> fallback = annotation.fallback();
fallbackMethod = fallback.getMethod(method.getName(), method.getParameterTypes());
fallbackMethodMap.put(configKey, fallbackMethod);
}
if (fallbackMethod == null) {
throw new RuntimeException("fallbackMethodMap not found");
}
return fallbackMethod.invoke(fallbackObj, args);
}
}
然后在 spring 容器中注册这个bean就行
@Bean
CusFeignBuilder cusFeignBuilder(){
return new CusFeignBuilder();
}
方案二
集成 sentinel ,今天写博客再回头看源码时候才发现的
加入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置开启
feign.sentinel.enabled=true
手动实现feign 接口,将实体类注册到 spring 中
@Component
public class DeviceApiFallBack implements DeviceApi{
@Override
public ServerResponse<String> login(String appId) {
return ServerResponse.createByErrorMessage("请求失败");
}
}
其实看代码知道原理一样,无非实现方式不一样
两个方案其实都行,方案一自己实现代码量多,方案二sentinel 官方实现,但是需要引入依赖,增加复杂度,而且 接口实现需要注册到spring 中
目前我选的还是方案一,简单。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341