Spring Openfeign与Ribbon,Hystrix的调用流程分析

2021-11-02 09:55:16 浏览数 (1)

Spring Openfeign作为一个声明式的REST Client,可以为应用中,尤其是微服务之间的调用上节省很多工作量,同时跟同为Netflix体系的Ribbon和Hystrix整合使用,可以为系统提供客户端负载均衡以及熔断保障。

以下内容参阅 spring-boot-2.5.x/Hystrix-2.0.x/spring-cloud-openfeign-2.0.x/feign-feign-11

配置

通过META/spring.factories加载的配置

代码语言:txt复制
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,
org.springframework.cloud.openfeign.FeignAutoConfiguration,
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration

openfeign META-INF/spring.factories有上述配置(spring ioc在初始化过程中会自动加载这些配置,其原理和Java SPI机制基本一致,你可以参阅SpringApllication#getSpringFactoriesInstances)。

1.FeignRibbonClientAutoConfiguration Ribbon配置

代码语言:txt复制
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })  //引入Feign和Ribbon依赖时本配置生效
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class) //在FeignAutoConfiguration之前加载
@EnableConfigurationProperties({ FeignHttpClientProperties.class }) //让FeignHttpClientProperties.class生效
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })
  • @AutoConfigureBefore(FeignAutoConfiguration.class)说明这个配置将在FeignAutoConfiguration之前加载。
  • @EnableConfigurationProperties ({ FeignHttpClientProperties.class }),让FeignHttpClientProperties.class生效,该类读取feign.httpclient开头的相关配置
  • @Import{...} 加载FeignLoadBanlancer相关配置,除了默认配置,其他两个是当我们的项目中引入了ApacheHttpClient或OkHttpClient才会生效的配置。核心是这些配置中都包含了feignClient方法,讲fegen.client注册到上下文中

2.FeignAutoConfiguration Feign配置

  • 加载FeignClientProperties.class,和FeignHttpClientProperties.class
  • 创建一个feignContext注册到当前上下文中,feignContext扩展自NamedContextFactory,是一个context工厂,会根据feignclient的名称创建context,也就是说,每个不同名称的feignclient都会创建一个独有的context。该context是AnnotationConfigApplicationContext,feignclient相关的配置和bean也会注册到这个context中。FeignClientsConfiguration作为这个context的默认配置,将在创建context时加载。FeignClientsConfiguration里处理关于feign encoder,decoder,Feign.Builder等关键配置Bean。
  • 如果有引入hystrix,这里还会注册HystrixTargeter到applicationcontext中,如果没有hystix依赖,则会使用默认的DefaultTargeter。
  • 如果没有loacbalancer相关的依赖(没有ribbon相关依赖),且有ApacheHttpClient或OkHttpClient依赖,会配置ApacheHttpClient或OkHttpClient作为feign.client(ApacheHttpClient是默认配置,OkHttpClient需要在配置中enable)

3.Gzip相关配置

@AutoConfigureAfter(FeignAutoConfiguration.class),均是在FeignAutoConfiguration之后配置,加载和压缩相关的配置信息并注册相关的拦截器

配置基本流程

spring cloud-spring openfeign factories.jpgspring cloud-spring openfeign factories.jpg

Import BeanDefinition

在应用类上添加Enablexxx以启用相关功能

@EnableFeignClients和FeignClientsRegistrar.class

EnableFeignClients注解 @Import(FeignClientsRegistrar.class),FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,这个接口的registerBeanDefinitions方法将在ConfigurationClassPostProcessor处理时调用,你可以参阅ConfigurationClassPostProcessor#processConfigBeanDefinitions和ConfigurationClassBeanDefinitionReader#ConfigurationClassBeanDefinitionReader

FeignClientsRegistrar的registerBeanDefinitions主要是加载了Feign的默认配置以及通过FeignClientFactoryBean为每个应用中的feignclient注册了FactoryBean的BeanDefinition。

FeignClientFactoryBean.class

FeignClientFactoryBean实现了FactoryBean<Object>接口,在spring ioc refresh流程中,对于FactoryBean类型的类将会调用getObject方法获取实例Bean,也就是feignclient。FeignClientFactoryBean中主要有和feignclient bean相关的type,name,url,path,decode,fallback,fallbackfactory等属性,而这些属性就是在配置中已加载到feigncontext中的内容。

代码语言:txt复制
	<T> T getTarget() {
		FeignContext context = applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
		...
	}

	protected Feign.Builder feign(FeignContext context) {
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(this.type);

		// @formatter:off
		Feign.Builder builder = get(context, Feign.Builder.class)
				// required values
				.logger(logger)
				.encoder(get(context, Encoder.class))
				.decoder(get(context, Decoder.class))
				.contract(get(context, Contract.class));
		// @formatter:on

		configureFeign(context, builder);

		return builder;
	}

	protected <T> T get(FeignContext context, Class<T> type) {
		T instance = context.getInstance(this.name, type);
		if (instance == null) {
			throw new IllegalStateException("No bean found of type "   type   " for "
					  this.name);
		}
		return instance;
	}

主要流程就是从context中获取已配置好的相关target,loadbalancer,encoder,decoder等对feignclient进行配置。而get(FeignContext context, Class<T> type) 则确认相关配置都会对应feign name的context中获取或创建。

Import配置信息

spring cloud-Feign Import Configuration.jpgspring cloud-Feign Import Configuration.jpg

创建FeignClient

调用一次feign client方法时的流程简述

假设我们用HelloFeign调用一次helloWorld方法,同时已经引入hystrix和ribbon并做了相关配置。

代码语言:txt复制
@FeignClient(value = "remote-server", fallback = HelloFeignFallback.class)
public interface HelloFeign {
	@GetMapping(value = "/helloworld", method = RequestMethod.POST)
    String helloWorld();
}

@Autowired
HelloFeign helloFeign;

res = helloFeign.helloWorld();

创建FeignClient

前文中已经对FeignClientFactoryBean进行了说明,在getObject方法中,将创建feignclient,且未为每一个feignclient根据client名称创建一个feigncontext,加载相关配置。feign是一种声明式得REST client,他本身并不处理网络请求,负载均衡,熔断等等相关得事务,feignclient主要是保存相关得配置和代理信息,在实际调用方法时实际通过代理得方式将网络请求代理给像ribbon或者hystrix处理。

  • feign.Builder 前文中提到每个feignclient都会创建一个feign context,feigncontext通过FeignClientsConfiguration加载 encoder,decoder,Feign.Builder等配置Bean,其中feign.builder在有hystrix依赖时,将返回HystrixFeign.builder()。在FeignClientFactoryBean#configureFeign中,将对builder配置相关得decoder,encoder等信息
代码语言:txt复制
<T> T getTarget() {
		FeignContext context = applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
		//省略...
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient)client).getDelegate();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
				this.type, this.name, url));
	}

在FeignClientFactoryBean#getTarget方法中,在实例化feignclient时,设置了LoadBalancerFeignClient到builder中。之后通过HystrixTargeter.target方法创建feignclient实例

  • feign.Target FeignAutoConfiguration#HystrixFeignTargeterConfiguration将HystrixTargeter做为feignTarget。
  • HystrixTargeter HystrixTargeter实现了了feign.Targeter接口,实际对外只有一个target方法。通过判断是否有fallback和fallbackfactory,分别调用HystrixFeign.builder对应得target方法
  • HystrixFeign HystrixFeign是feign中得一个项目,用于集成hystrix。HystrixFeign.Buidler扩展自Feign.Builder,target方法中,对fallback使用FallbackFactory封装,并调用Feign.Builder的invocationHandlerFactory将HystrixInvocationHandler设置为InvacationHandler。最后通过Feign.Builder.build方法返回Feign对象
  • Feign
代码语言:txt复制
    public Feign build() {
		//省略Client,logger,Encoder等等配置信息...
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
  }

Feign.build方法设置SynchronousMethodHandler和ParseHandlersByName等一系列配置信息后,返回一个ReflectiveFeign对象,ReflectiveFeign的newInstance将在HystrixFeign的target方法中调用。

  • ReflectiveFeign
代码语言:txt复制
//ReflectiveFeign
  public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

newInstance实际就是将我们声明式的FeignClient中的方法,通过Proxy进行代理,所有我们定义的方法都将由InvocationHandler进行处理,就是HystrixInvocationHandler。

自此,一个FeignClient就创建结束了,实际我们定义的方法都是由Proxy的InvocationHandler进行处理。

spring cloud-FeignClient Create.jpgspring cloud-FeignClient Create.jpg

调用流程

代码语言:txt复制
res = helloFeign.helloWorld();
  • InvocationHandler.invoke 当调用FeignClient方法时,由前文可知,helloWorld()这个方法,实际是被代理的,实际会调用InvocationHandler的invoke方法。
代码语言:txt复制
  @Override
  public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {
    if ("equals".equals(method.getName())) {
      //...
    } //...

    HystrixCommand<Object> hystrixCommand =
        new HystrixCommand<Object>(setterMethodMap.get(method)) {
          @Override
          protected Object run() throws Exception {
            try {
              return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
            } //...
          }

          @Override
          protected Object getFallback() {
            if (fallbackFactory == null) {
              return super.getFallback();
            }
            try {
              Object fallback = fallbackFactory.create(getExecutionException());
              Object result = fallbackMethodMap.get(method).invoke(fallback, args);
              if (isReturnsHystrixCommand(method)) {
                return ((HystrixCommand) result).execute();
              } //...
            }
          }
        };

    if (Util.isDefault(method)) {
      return hystrixCommand.execute();
    }  //..
    return hystrixCommand.execute();
  }

invoke中对调用做了HystrixCommand封装,并做了fallback处理。根据hystrix isolation策略,可能会在隔离的线程或同样的线程调用run方法。就是HystrixInvocationHandler.this.dispatch.get(method).invoke(args)。这里的dispatch就是ReflectiveFeign调用create方法创建InvocationHandler时传入的Map<Method, MethodHandler> methodToHandler,而对于非default方法的handler,主要由SynchronousMethodHandler.invoke处理

  • SynchronousMethodHandler.invoke
代码语言:txt复制
  @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        continue;
      }
    }
  }

invoke方法是执行调用的处理位置,这里对retryer重试逻辑进行了处理,同时executeAndDecode中会执行client的execute方法,在本例中就是ribbon的execute方法,最终返回结果。

调用流程

spring cloud-spring openfeign call.jpgspring cloud-spring openfeign call.jpg

spring在onfresh流程中,解决Bean的Autowired注解,将helloFeign注入的service中。

0 人点赞