How to customize feign call to realize hystrix timeout and abnormal fuse
- 2021-10-11 18:42:37
- OfStack
Requirements description
feign integration with hystrix is frequently used in the spring cloud project, but recently hystrix has been found to be powerful, but it is a bit overqualified for us.
First of all, I only need his one fuse function, that is to say, the request timeout, abnormal return to fallback configured in FeignClient annotation, no need for non-blocking operation, and no need to retry. When hystrix calls feign, it does thread pool isolation processing, which increases the complexity of the project (thread pool parameter configuration, fewer threads request service direct rejection, more thread management...)
At present, feign throws an exception directly after timeout. In this way, although it is blown in time, the normal program logic does not go, and the configured fallback has no effect. This configuration item must cooperate with hystrix.
What I need is this effect
try{
feign.api();
}catch(){
return fallback();
}
But every feign call manually adds try... catch is too low, and it is best to write something similar to section 1.
At this time, I thought of hystrix. Since the framework has been done, I will look at the code directly. Is copy not finished?
Source code learning
Two days ago, I published an article about feign and hystrix call integration
Based on previous analysis of key code
HystrixInvocationHandler (feign.hystrix)
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
.............
// setterMethodMap Encapsulation hystrixCommand Configuration information (timeout, retry or not ..... )
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();
}
According to the previous analysis of the source code, you can see directly where it was called. hystrix actually encapsulates an feign. Builer class name is feign. hystrix. HystrixFeign. Builder uses the builder mode, and the generated class is used when calling the service
See the key build () method
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
// Redefine 1 A InvocationHandler Realization Similar aop Effect
@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 dynamic proxy I don't say much here, the core is InvocationHandler (if it is jdk dynamic proxy), then feign is also here, let's look at feign call declaration is an interface, in fact, spring dynamic proxy generates proxy class, when calling method, what is actually called is
java.lang.reflect.InvocationHandler#invoke
Programme conception
Then we just need to learn from hystrix, implement an feign. build by ourselves, and replace InvocationHandler with our own.
Then call feign official InvocationHandler in our own InvocationHandler, that is
feign.hystrix.HystrixInvocationHandler#invoke
In this method
this.dispatch.get(method).invoke(args);
This code
Specific code implementation of the scheme
Option 1
Self-realization
import feign.Feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
/**
* Customize feign Build
* @author hgf
*/
public class CusFeignBuilder extends Feign.Builder{
public CusFeignBuilder() {
this.invocationHandlerFactory((target, dispatch) -> {
Class<?> type = target.type();
FeignClient annotation = type.getAnnotation(FeignClient.class);
// Structure fallback Instances
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;
/**
* Custom feign Call
*/
@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("{} Request Exception occurs ==> {}", configKey, throwable.getMessage());
try {
return getFallbackReturn(method, args, throwable);
} catch (Throwable e) {
throw throwable;
}
}
}
/**
* Reflection call {@link FeignClient#fallback()} Generate failure return value
* @param method Current feign Method
* @param args Parameter
* @param throwable Anomaly
*/
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");
}
// Failure return
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);
}
}
Then register the bean in the spring container
@Bean
CusFeignBuilder cusFeignBuilder(){
return new CusFeignBuilder();
}
Option 2
Integration of sentinel, today write a blog and then look back at the source code found
Add dependency
HystrixInvocationHandler (feign.hystrix)
0
Configuration on
HystrixInvocationHandler (feign.hystrix)
1
Manually implement the feign interface and register entity classes in spring
HystrixInvocationHandler (feign.hystrix)
2
In fact, look at the code to know the principle of 1, but the implementation method is not 1
In fact, both schemes are OK. Scheme 1 has a large amount of code, and Scheme 2sentinel is officially implemented, but it needs to introduce dependency and increase complexity, and the interface implementation needs to be registered in spring
At present, I choose Scheme 1, which is simple.