This is the 17th day of my participation in the August Text Challenge.More challenges in August

Problem description

Suppose there are three projects A, B, and C, where A and B are the service providers and C is the caller. Different timeout times are required when C calls A and B. For example, C calls A with A timeout of 2s and B with A timeout of 3s… Thought is very simple thing, but when programming for baidu search to find no, website also have no, it’s a hard, small make up was one of those will not take the initiative to study the source code, is the project there is a need or see someone transformed what stuff is particularly interesting, don’t go meow, now had no choice but to study wave source, manual.

The body of the

The text is divided into three parts

  • Source research
  • Proposed scheme
  • Plan implementation

Source research

First, if it finds the key source code, if the hystrix feign integration familiar friend, you can skip, see scheme directly, if you want to know how to get to the source of the friend suggested a look, it took me quite a long period of time, online source code parsing is only show the key code, but how can I get the details, no description, Otherwise I wouldn’t have spent a long time researching and reading.

Hystrix and Feign

First of all, we know that FeIGN is used in Spring Cloud to make calls between services. OpenFeign integrates ribbon to achieve load balancing of actual requests

Hystrix is a fuse, which is used to fail some tasks in a timely manner instead of hanging threads, causing the cascade of servers to collapse. If a service timeout occurs when feign calls between microservices, Hystrix fuses and returns results immediately.

The key code

If you go online and look up Hystrix timeout configuration, this is what happens

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000
Copy the code

Then the Hystrix component must parse this configuration and call it when it works

Spring Boot configurations are named {component name}Properties, so a search will find the key configuration class

com.netflix.hystrix.HystrixCommandProperties

As you can see, all properties are final, that is, they cannot be set, so only constructors or static code blocks can be changed. The latter is obviously not suitable, look for constructors to see

That’s how it looks! Just click on one of them

private static HystrixProperty<Boolean> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Boolean builderOverrideValue, Boolean defaultValue) {
        return forBoolean()
                .add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue)
                .add(propertyPrefix + ".command.default." + instanceProperty, defaultValue)
                .build();
    }
Copy the code

HystrixCommandKey = HystrixCommandKey = HystrixCommandKey = HystrixCommandKey = HystrixCommandKey

Invoke :104, HystrixInvocationHandler (feign.hystrix)

@Override
  public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {...// setterMethodMap encapsulates hystrixCommand configuration information (timeout, retry.....)
    HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
      @Override
      protected Object run(a) throws Exception {... HystrixInvocationHandler.this.dispatch.get(method).invoke(args); . }@Override
      protected Object getFallback(a) {... }}; .return hystrixCommand.execute();
  }
Copy the code

So basically that’s it, it’s just hystrixCommand calling Feign, and the main thing is where the setterMethodMap is set up from,

final class HystrixInvocationHandler implements InvocationHandler {
  
  private finalTarget<? > target;private final Map<Method, MethodHandler> dispatch;
  private finalFallbackFactory<? > fallbackFactory;// Nullable
  private final Map<Method, Method> fallbackMethodMap;
  private finalMap<Method, Setter> setterMethodMap; HystrixInvocationHandler(Target<? > target, Map<Method, MethodHandler> dispatch, SetterFactory setterFactory, FallbackFactory<? > fallbackFactory) {this.target = checkNotNull(target, "target");
    this.dispatch = checkNotNull(dispatch, "dispatch");
    this.fallbackFactory = fallbackFactory;
    this.fallbackMethodMap = toFallbackMethod(dispatch);
    this.setterMethodMap = toSetters(setterFactory, target, dispatch.keySet()); }}Copy the code

It is also final, so it is also assigned by the constructor

target:56, HystrixTargeter (org.springframework.cloud.openfeign)

Look at the core of the code here

// HystrixTargeter is a class that corresponds to the @feignClient interface
@SuppressWarnings("unchecked")
class HystrixTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget
       
         target)
        {... feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;// Get the SetterFactory configuration class in the container
		SetterFactory setterFactory = getOptional(factory.getName(), context,
			SetterFactory.class);
		if(setterFactory ! =null) {
			builder.setterFactory(setterFactory);
		}
   
    // Read from @feignClient annotation or defaultClass<? > fallback = factory.getFallback();if(fallback ! =void.class) {
			returntargetWithFallback(factory.getName(), context, target, builder, fallback); } Class<? > fallbackFactory = factory.getFallbackFactory();if(fallbackFactory ! =void.class) {
			return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
		}

		returnfeign.target(target); }...private <T> T getOptional(String feignClientName, FeignContext context, Class
       
         beanType)
        {
		returncontext.getInstance(feignClientName, beanType); }}Copy the code

Look at the feign. Hystrix. SetterFactory

public interface SetterFactory {


  HystrixCommand.Setter create(Target
        target, Method method);

	// Default implementation
  final class Default implements SetterFactory {

    @Override
    public HystrixCommand.Setter create(Target
        target, Method method) {
      String groupKey = target.name();
      String commandKey = Feign.configKey(target.type(), method);
      // HystrixCommandKey and group assign values
      returnHystrixCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); }}}Copy the code

The HystrixCommandProperties key for the key configuration information is specified here

HystrixCommandProperties = HystrixCommandProperties = HystrixCommandProperties = HystrixCommandProperties = HystrixCommandProperties = HystrixCommandProperties = HystrixCommandProperties = HystrixCommandProperties

Proposed scheme

So if you look at the code and you find that the SetterFactory interface is the key, and this is injected it’s as simple as implementing this interface manually and injecting it into the Spring container

In feign. Hystrix. SetterFactory. Default# manually in the create method to realize different feign interface configuration, even different feign

I’m doing it with annotations right now, but you can do it with rules like method names

The ultimate goal is for the specified feign method to get the specified configuration

@FeignClient(value = "itemRobot", path = "cloud/device")
public interface DeviceApi {

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    ServerResponse<String> login(@RequestParam String appId);
  
   @RequestMapping(value = "/logout", method = RequestMethod.GET)
    ServerResponse<String> logout(@RequestParam String appId);
}
Copy the code
# login() method mapping
hystrix.command.login.execution.isolation.thread.timeoutInMilliseconds=10000
The # logout() method is mapped
hystrix.command.logout.execution.isolation.thread.timeoutInMilliseconds=10000
Copy the code

If the method is based on the level of different configuration, hystrix annotations for this kind of official have com.net flix. Hystrix. Contrib. Javanica. The annotation. HystrixCommand


	@HystrixCommand(groupKey="accountPayGroup",commandKey="accountPay",threadPoolKey="account",threadPoolProperties= { @HystrixProperty(name="coreSize",value="20"), @HystrixProperty(name="maxQueueSize",value="50") },commandProperties={ @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="3000"), @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="40") })

Copy the code

Ps: I used it and found it did not take effect. Currently, I am still debugging it. If there is progress, I will write another article

However, it is not possible to call different interface implementations based on Feign, so if you want to implement different configurations based on methods, you can use the official one. If you want to configure all methods in one interface, different interface implementations are different, then I can use the following method.

The specific implementation

Specify the annotation

@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface CusHystrixCommandKey {
   / / the corresponding commandKey
    String name(a);
}

Copy the code

Implement SetterFactory

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import feign.Feign;
import feign.Target;
import feign.hystrix.SetterFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;

@Component
public class MyHystrixFactory implements SetterFactory {

    @Override
    public HystrixCommand.Setter create(Target
        target, Method method) {
        String groupKey = target.name();
        String commandKey = Feign.configKey(target.type(), method);


        CusHystrixCommandKey annotation = method.getAnnotation(CusHystrixCommandKey.class);
        if (annotation == null) {
						If the specified method does not have a CusHystrixCommandKey annotation, use feignClient.value () as the key
            FeignClient feignClient = method.getDeclaringClass().getAnnotation(FeignClient.class);
            commandKey = feignClient.value();

        } else {
          // Otherwise get the specified name() as key
            commandKey = annotation.name();
        }


        returnHystrixCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); }}Copy the code

Test page will not paste, old method, universal breakpoint

Write in the last

Above the source code what parsing is wrong, trouble below message, thank you ha