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.


Related articles: