前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Openfeign与Ribbon,Hystrix的调用流程分析

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

原创
作者头像
TimGallin
修改2021-11-02 09:55:16
1.1K0
修改2021-11-02 09:55:16
举报
文章被收录于专栏:thinkythinky

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

以下内容参阅 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.jpg
spring cloud-spring openfeign factories.jpg

Import BeanDefinition

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

@EnableFeignClients和FeignClientsRegistrar.class

EnableFeignClients注解 @Import(FeignClientsRegistrar.class),FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,这个接口的registerBeanDefinitions方法将在ConfigurationClassPostProcessor处理时调用,你可以参阅ConfigurationClassPostProcessor#processConfigBeanDefinitionsConfigurationClassBeanDefinitionReader#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.jpg
spring 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.jpg
spring 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.jpg
spring cloud-spring openfeign call.jpg

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 配置
  • Import BeanDefinition
  • 创建FeignClient
  • 调用流程
相关产品与服务
负载均衡
负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档