openfeign源码阅读

opnefeign

用法

声明式

通过

@FeignClient(value = "${user.app.name}", fallback = UserSecurityServiceFeignFallback.class)
public interface UserSecurityServiceFeign {

    /**
     * 验证token,返回对应的权限信息
     *
     * @param authenticateTokenCmd token身份验证命令
     * @return 登录
     */
    @PostMapping("/auth/token/authentication")
    ResultDTO<UserPermissionInfoCO> authenticateToken(AuthenticateTokenCmd authenticateTokenCmd);

}
复制代码

继承式

feigin允许继承,通过继承我们可以覆盖一些配置进行自定义FeignClient。例如UserSecurityServiceFeign在API module中,然后在其他module我们可以引用API module然后定义一个FeignClient来继承它,然后进行自定义。

@FeignClient(value = "${user.app.name}", fallback = UserSecurityServiceFeignFallback2.class)
public interface SelfUserSecurityServiceFeign  extends  UserSecurityServiceFeign{
  
}
复制代码

原理分析

入口

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
   //需要扫描的包
   String[] value() default {};
   String[] basePackages() default {};
   Class<?>[] basePackageClasses() default {};
   //默认配置例如feign.codec.Decoder 、 feign.codec.Encoder 、 feign.Contract 这些。
   Class<?>[] defaultConfiguration() default {};
   Class<?>[] clients() default {};
}
复制代码

@EnableFeignClients开始,其用@Import注解导入了FeignClientsRegistrarFeignClientsRegistrar实现了ImportBeanDefinitionRegistrar也就意味着在BeanFactoryPostProcessor进行配置类解析时,会执行registerBeanDefinitions方法去注册@FeignClient对应的BeanDefintion。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   //注册默认的feign配置类
   registerDefaultConfiguration(metadata, registry);
   //注册FeignClients
   registerFeignClients(metadata, registry);
}
复制代码

注册默认的feign配置类

可以看到根据@EnableFeignClients注解配置的defaultConfiguration,构成一个**FeignClientSpecification**BeanDefinition然后注册到容器中

private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
   Map<String, Object> defaultAttrs = metadata
         .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

   if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
      String name;
      if (metadata.hasEnclosingClass()) {
         name = "default." + metadata.getEnclosingClassName();
      }
      else {
         name = "default." + metadata.getClassName();
      }
      registerClientConfiguration(registry, name,
            defaultAttrs.get("defaultConfiguration"));
   }
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {
	BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
	builder.addConstructorArgValue(name);
	builder.addConstructorArgValue(configuration);
	registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());
}
复制代码

注册FeignClients

最为核心的一步,BeanDefintion注册到Spring 容器中我们才能依赖注入FeignClient。根据注解配置扫描对应的包,如果class被@FeignClient注解标注那么就成为一个候选BeanDefinition,然后遍历候选的BeanDefinition读取其@FeignClient的配置元数据attributes生成一个个**FeignClientFactoryBean**-BeanDefinition注册到容器中。

//attributes  @FeignClient中读取的配置信息
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
   String className = annotationMetadata.getClassName();
   //重点!!!BeanDefinition类型为FeignClientFactoryBean
   BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
   validate(attributes);
   definition.addPropertyValue("url", getUrl(attributes));
   definition.addPropertyValue("path", getPath(attributes));
   String name = getName(attributes);
   definition.addPropertyValue("name", name);
   String contextId = getContextId(attributes);
   definition.addPropertyValue("contextId", contextId);
   //type 设置的FactoryBean 返回的 类型
   definition.addPropertyValue("type", className);
   definition.addPropertyValue("decode404", attributes.get("decode404"));
   definition.addPropertyValue("fallback", attributes.get("fallback"));
   definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
   definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

   String alias = contextId + "FeignClient";
   AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
   beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

   // has a default, won't be null
   boolean primary = (Boolean) attributes.get("primary");

   beanDefinition.setPrimary(primary);

   String qualifier = getQualifier(attributes);
   if (StringUtils.hasText(qualifier)) {
      alias = qualifier;
   }
   BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
         new String[] { alias });
   BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
复制代码

FeignClient的创建

根据上面的注册FeignClient的代码,可知FeignClient所注册的BeanDefinition是**FeignClientFactoryBean一个FactoryBean实现类,也就是在依赖注入时注入的是其getObject方法所返回的对象。源码里可以看到FeignClient是一个单例bean**,最终由**Feign.Builder创建Feign通过Feign(ReflectiveFeign)来创建一个JDK动态代理对象**-也就是FeignClient 实例

@Override
public Object getObject() throws Exception {
   return getTarget();
}
<T> T getTarget() {
   //1、获得Feign.Builder
   FeignContext context = applicationContext.getBean(FeignContext.class);
   Feign.Builder builder = feign(context);

   //2、如果没有配置url则配置时服务名需要进行负载均衡
   if (!StringUtils.hasText(url)) {
      //...省略url拼接
      //使用负载均衡client
      return (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url));
   }
   //...省略url拼接
  
   Client client = getOptional(context, Client.class);
   //此时有完整的url则需要去掉负载均衡的功能 即直接拿到装饰模式内部的Client
   if (client != null) {
      if (client instanceof LoadBalancerFeignClient) {
         client = ((LoadBalancerFeignClient) client).getDelegate();
      }
      if (client instanceof FeignBlockingLoadBalancerClient) {
         client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
      }
      builder.client(client);
   }
   Targeter targeter = get(context, Targeter.class);
   //targeter创建client target方法最终返回的都是feign.target  tartet类型是@FeignClient的Interface
   return (T) targeter.target(this, builder, context,new HardCodedTarget<>(type, name, url));
}
@Override
public boolean isSingleton() {
    //feignClient是单例
	return true;
}
//Feign.builder 的target方法 创建一个动态代理对象
public <T> T target(Target<T> target) {
    return build().newInstance(target);
}

//负载均衡
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {
    //容器中获取Client
	Client client = getOptional(context, Client.class);
	if (client != null) {
		builder.client(client);
		Targeter targeter = get(context, Targeter.class);
		return targeter.target(this, builder, context, target);
	}
	throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

复制代码

Feign.Builder

feign的构建器,它是feign的核心决定了Feigin的构建方式(也确定了整体逻辑流程),通过它可以进行扩展例如整合Hystrix、sentinel都是暴露一个Feign.BuilderBean来整合自己的逻辑。可以看到Feign.Builder确实是从容器中获取的(FeignContext是NamedContextFactory)。

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

   // 从容器中获取builder
   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;
}
  //Feign.Builder 所预留的扩展
  public static class Builder {

    private final List<RequestInterceptor> requestInterceptors =
        new ArrayList<RequestInterceptor>();
    private Logger.Level logLevel = Logger.Level.NONE;
    //方法上注解解析  
    private Contract contract = new Contract.Default();
    //client 不同功能的客户端 例如负载均衡
    private Client client = new Client.Default(null, null);
    private Retryer retryer = new Retryer.Default();
    private Logger logger = new NoOpLogger();
    //解码和编码 
    private Encoder encoder = new Encoder.Default();
    private Decoder decoder = new Decoder.Default();
    private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
    //异常处理 
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    private Options options = new Options();
    //核心:生成代理对象的拦截器 FeignClient 时JDKdongd
    private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
    private boolean decode404;
    private boolean closeAfterDecode = true;
    private ExceptionPropagationPolicy propagationPolicy = NONE;
    private boolean forceDecoding = false;
    private List<Capability> capabilities = new ArrayList<>();
      
     public Feign build() {
      //其他代码省略  
      //生成Feign
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }  
 }
复制代码

动态代理

ReflectiveFeign中创建了JDK代理对象,根据方法上的注解信息(Contract解析)创建MethodHandler,在InvocationHandler中根据Method对应MethodHandler去执行对应的逻辑。

//目标
public <T> T newInstance(Target<T> target) {
  //解析方法上注解创建对应的MethodHandler
  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()) {
     //Oject中方法忽略
    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 feign中InvocationHandlerFactory 
  InvocationHandler handler = factory.create(target, methodToHandler);
  //JDK动态代理  
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
      new Class<?>[] {target.type()}, handler);

  for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}
复制代码

InvocationHandlerFactory

InvocationHandler工厂,扩展时需要覆盖其Feign.Bulider中invocationHandlerFactory。

public interface InvocationHandlerFactory {
  //dispatch 分发根据Method找对对应的MethodHandler
  InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);

  interface MethodHandler {

    Object invoke(Object[] argv) throws Throwable;
  }

  static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }
}

复制代码

FeignContext

feign上下文继承**NamedContextFactory,它为每个客户端都创建一个ApplicationContext,并从这个ApplicationContext中获取需要的Bean。它实现了ApplicationContextAware所以能够拿到父容器**

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
      implements DisposableBean, ApplicationContextAware {	
      // name 为客服端名称,也就是服务名
      protected AnnotationConfigApplicationContext getContext(String name) {
         //double check+synchronized 保证每个client对应的ApplicationContext只有一个  参考单例模式
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
                    //如果没有则创建加入map中
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
        
	}
    //省略其他
   	@Override
	public void setApplicationContext(ApplicationContext parent) throws BeansException {
        //拿到父容器
		this.parent = parent;
	} 
}
复制代码

创建SpringApplicationContext

创建一个新的AnnotationConfigApplicationContext,将自动装配得到的FeignClientSpecification注册进新的ApplicationContext,然后setParent组成父子层次ApplicationContext,最后启动容器(context.refresh())。

protected AnnotationConfigApplicationContext createContext(String name) {
   //创建新的ApplicationContext
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
   //将自动配置的FeignClientSpecification
   if (this.configurations.containsKey(name)) {
      for (Class<?> configuration : this.configurations.get(name)
            .getConfiguration()) {
         context.register(configuration);
      }
   }
   for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
      if (entry.getKey().startsWith("default.")) {
         for (Class<?> configuration : entry.getValue().getConfiguration()) {
            context.register(configuration);
         }
      }
   }
   context.register(PropertyPlaceholderAutoConfiguration.class,
         this.defaultConfigType);
   context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
         this.propertySourceName,
         Collections.<String, Object>singletonMap(this.propertyName, name)));
   //关键组成父子层次上下文
   if (this.parent != null) {
      // Uses Environment from parent as well as beans
      context.setParent(this.parent);
      // jdk11 issue
      // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
      context.setClassLoader(this.parent.getClassLoader());
   }
   context.setDisplayName(generateDisplayName(name));
   //启动容器
   context.refresh();
   return context;
}
复制代码

扩展

如果需要自定义Feign,我们只需要配置一个自定义Feign.Builder重新build 方法。其他扩展请查看Feign.Builder中各个属性。

限流熔断扩展

Sentinel、Hystrix对feign的支持,主要是覆盖了invocationHandlerFactory使用了自己的SentinelInvocationHandler在feign代理对象的执行时加入Sentinel的核心代码。以下为Sentinel对feign的支持。

public final class SentinelFeign {
   public static Builder builder() {
      return new Builder();
   }
   //自定义Feign.Builder
   public static final class Builder extends Feign.Builder implements ApplicationContextAware {
      //重写build方法
      @Override
      public Feign build() {
         //设置自定义invocationHandlerFactory
         super.invocationHandlerFactory(new InvocationHandlerFactory() {
            @Override
            public InvocationHandler create(Target target,Map<Method, MethodHandler> dispatch) {
               //省略其他配置读取代码
               return new SentinelInvocationHandler(target, dispatch);
            }
            //其他invocationHandlerFactory代理码
         });

         super.contract(new SentinelContractHolder(contract));
         return super.build();
      }
	//省略其他
}
//SentinelInvocationHandler 核心 代理模式拦截方法执行加入sentinel核心逻辑
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)throws Throwable {
		
	String resourceName = methodMetadata.template().method().toUpperCase()
						+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
	Entry entry = null;
	try {
        //sentinel 模板代码
		ContextUtil.enter(resourceName);
		entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
        //具体执行逻辑
		result = methodHandler.invoke(args);
	}catch (Throwable ex) {
	 //异常处理				
	}
	finally {
		if (entry != null) {
			entry.exit(1, args);
		}
		ContextUtil.exit();
	}
   //其他代码省略
	return result;
}   
复制代码

负载均衡扩展

feign默认继承Ribbon做负载均衡,其原理是在FeignClientFactoryBean创建FeignClient时如果发现配置为服务名时则进行负载均衡。从容器中获取ClientBean对象,然后设置到Builder中。Client中包含负载均衡逻辑。

  //判断是否需要是需要负载均衡 
  if (!StringUtils.hasText(url)) {
      if (!name.startsWith("http")) {
         url = "http://" + name;
      }
      else {
         url = name;
      }
      url += cleanPath();
      //创建负载均衡的feignClient
      return (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url));
   }
//负载均衡
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {
    //
	Client client = getOptional(context, Client.class);
	if (client != null) {
		builder.client(client);
		Targeter targeter = get(context, Targeter.class);
		return targeter.target(this, builder, context, target);
	}
	throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
复制代码

LoadBalancer负载均衡

FeignLoadBalancerAutoConfiguration

feign 负载均衡自动装配,根据当前feign客户端类型装饰不同的负载均衡Client。

@Import({ HttpClientFeignLoadBalancerConfiguration.class,
      OkHttpFeignLoadBalancerConfiguration.class,
      DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {

}
复制代码

以OkHttp为例当feign.okhttp.enabled=true,将okHttpClient先装饰为OkHttpClient在装饰为FeignBlockingLoadBalancerClient(包含负载均衡client)。


@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancerConfiguration {

   @Bean
   @ConditionalOnMissingBean
   @Conditional(OnRetryNotEnabledCondition.class)
   public Client feignClient(okhttp3.OkHttpClient okHttpClient,BlockingLoadBalancerClient loadBalancerClient) {
      OkHttpClient delegate = new OkHttpClient(okHttpClient);
      //装饰为 FeignBlockingLoadBalancerClient 带有负载均衡功能
      return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
   }

   @Bean
   @ConditionalOnMissingBean
   @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
   @ConditionalOnBean(LoadBalancedRetryFactory.class)
   @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled",
         havingValue = "true", matchIfMissing = true)
   public Client feignRetryClient(BlockingLoadBalancerClient loadBalancerClient,
         okhttp3.OkHttpClient okHttpClient,
         List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
      AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
      OkHttpClient delegate = new OkHttpClient(okHttpClient);
      return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient,
            loadBalancedRetryFactories.get(0));
   }

}
复制代码
BlockingLoadBalancerClientAutoConfiguration

自动装配BlockingLoadBalancerClient

Ribbon负载均衡

feign默认的负载均衡器

FeignRibbonClientAutoConfiguration

fegin的Ribbon负载均衡器自动装配

//如果正在使用 Feign 并且需要使用 Ribbon 作为负载均衡器,则将激活自动配置
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
//默认使用Ribbon
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
//对不同的Http请求Client 做不同的装饰
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
      OkHttpFeignLoadBalancedConfiguration.class,
      DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
  //省略其他配置
}
复制代码

以OkHttp为例,当feign.okhttp.enabled=true触发以下的自动装配配置类-将okhttp3.OkHttpClient委托给LoadBalancerFeignClient装饰模式)以获得负载均衡的功能。

SpringClientFactory为创建客户端、负载均衡器和客户端配置实例的工厂。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration {

   @Bean
   @ConditionalOnMissingBean(Client.class)
   public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
         SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
      OkHttpClient delegate = new OkHttpClient(okHttpClient);
      return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
   }

}
复制代码
代码配置 Ribbon

配置 Ribbon 最简单的方式就是通过配置文件实现。当然我们也可以通过代码的方式来配置。

通过代码方式来配置之前自定义的负载策略,首先需要创建一个配置类,初始化自定义的策略,代码如下所示。

@Configurationpublic class BeanConfiguration {   
    @Bean   
    public MyRule rule() {       
        return new MyRule();    
    }
}
复制代码

创建一个 Ribbon 客户端的配置类,关联 BeanConfiguration,用 name 来指定调用的服务名称,代码如下所示。

@RibbonClient(name = "ribbon-config-demo", configuration = BeanConfiguration.class)
public class RibbonClientConfig {
    
}
复制代码
配置文件方式配置 Ribbon

除了使用代码进行 Ribbon 的配置,我们还可以通过配置文件的方式来为 Ribbon 指定对应的配置:

格式:<clientName>.<nameSpace>.<propertyName>=<value>

<clientName>.ribbon.NFLoadBalancerClassName: Should implement ILoadBalancer(负载均衡器操作接口)
<clientName>.ribbon.NFLoadBalancerRuleClassName: Should implement IRule(负载均衡算法)
<clientName>.ribbon.NFLoadBalancerPingClassName: Should implement IPing(服务可用性检查)
<clientName>.ribbon.NIWSServerListClassName: Should implement ServerList(服务列表获取)
<clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerList­Filter(服务列表的过滤)
复制代码

clientName在feign中就是服务名称,ribbon为命名空间

配置读取原理-IClientConfig

IClientConfig默认实现中(DefaultClientConfigImpl),根据restClientName读取restClientName前缀的properties

@Override
public void loadProperties(String restClientName){
       enableDynamicProperties = true;
       setClientName(restClientName);
        //加载默认配置
       loadDefaultValues();
       //读取前缀为restClientName所对应的属性
       Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName);
       for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){
           String key = keys.next();
           String prop = key;
           try {
               if (prop.startsWith(getNameSpace())){
                   prop = prop.substring(getNameSpace().length() + 1);
               }
               //设置到IClientConfig中
               setPropertyInternal(prop, getStringValue(props, key));
           } catch (Exception ex) {
               throw new RuntimeException(String.format("Property %s is invalid", prop));
           }
       }
   }
   
复制代码

具体的key查看IClientConfigKey的实现类CommonClientConfigKey例如:

//LoadBalancer Related
public static final IClientConfigKey<Boolean> InitializeNFLoadBalancer = new CommonClientConfigKey<Boolean>("InitializeNFLoadBalancer"){};

public static final IClientConfigKey<String> NFLoadBalancerClassName = new CommonClientConfigKey<String>("NFLoadBalancerClassName"){};

public static final IClientConfigKey<String> NFLoadBalancerRuleClassName = new CommonClientConfigKey<String>("NFLoadBalancerRuleClassName"){};

public static final IClientConfigKey<String> NFLoadBalancerPingClassName = new CommonClientConfigKey<String>("NFLoadBalancerPingClassName"){};

public static final IClientConfigKey<Integer> NFLoadBalancerPingInterval = new CommonClientConfigKey<Integer>("NFLoadBalancerPingInterval"){};

public static final IClientConfigKey<Integer> NFLoadBalancerMaxTotalPingTime = new CommonClientConfigKey<Integer>("NFLoadBalancerMaxTotalPingTime"){};
复制代码
默认Spring-cloud-netfix-ribbon配置

DefaultClientConfigImpl.loadDefaultValues中,例如

public void loadDefaultValues() {
    //省略其他配置只贴出来了负载均衡器。
    //ZoneAwareLoadBalancer
    putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerClassName, getDefaultNfloadbalancerClassname());
    //默认的负载规则 AvailabilityFilteringRule 但是RibbonClientConfiguration覆盖这个默认
    putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerRuleClassName, getDefaultNfloadbalancerRuleClassname());
    putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerPingClassName, getDefaultNfloadbalancerPingClassname());
   
}
复制代码
ZoneAwareLoadBalancer

默认的负载均衡器

@Override 
public Server chooseServer(Object key) {
        //如果sh
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            LoadBalancerStats lbStats = getLoadBalancerStats();
            Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
            logger.debug("Zone snapshots: {}", zoneSnapshot);
            if (triggeringLoad == null) {
                triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
            }

            if (triggeringBlackoutPercentage == null) {
                triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
            }
            Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
            logger.debug("Available zones: {}", availableZones);
            if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if (zone != null) {
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    server = zoneLoadBalancer.chooseServer(key);
                }
            }
        } catch (Exception e) {
            logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
        }
        if (server != null) {
            return server;
        } else {
            logger.debug("Zone avoidance logic is not invoked.");
            return super.chooseServer(key);
        }
    }
复制代码

ribbon自带7种负载均衡规则实现:

  • com.netflix.loadbalancer.RoundRobinRule – 轮询
  • com.netflix.loadbalancer.RandomRule – 随机
  • com.netflix.loadbalancer.RetryRule – 重试,先按RoundRobinRule进行轮询,如果失败就在指定时间内进行重试
  • com.netflix.loadbalancer.WeightedResponseTimeRule – 权重,响应速度越快,权重越大,越容易被选中。
  • com.netflix.loadbalancer.BestAvailableRule – 先过滤掉不可用的处于断路器跳闸转态的服务,然后选择一个并发量最小的服务
  • com.netflix.loadbalancer.AvailabilityFilteringRule -先过滤掉故障实例,再选择并发量较小的实例
  • com.netflix.loadbalancer.ZoneAvoidanceRule – Spring-cloud-netfix-ribbon默认规则 复合判断server所在区域的性能和server的可用性进行服务的选择。
ZoneAvoidanceRule

Spring-cloud-netfix-ribbon默认的负载均衡策略,在RibbonClientConfiguration中如果配置文件中没有对应负载均衡规则配置则创建一个**ZoneAvoidanceRule**,其实它使用的负载均衡算法就是轮询

@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
	if (this.propertiesFactory.isSet(IRule.class, name)) {
		return this.propertiesFactory.get(IRule.class, config, name);
	}
	ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
	rule.initWithNiwsConfig(config);
	return rule;
}
//默认实现
public Server choose(Object key) {
    ILoadBalancer lb = getLoadBalancer();
    //轮询之后过滤
    Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
    if (server.isPresent()) {
       return server.get();
    } else {
       return null;
    }       
}
 public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
     //得到有资格的server
     List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
     if (eligible.size() == 0) {
          return Optional.absent();
     }
     //incrementAndGetModulo 有资格的server进行轮询 即cas获取下一个下标值
     return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
 }
//自增并取模 并cas替换直到成功即得到轮询后下标
 private int incrementAndGetModulo(int modulo) {
 	for (;;) {
       int current = nextIndex.get();
       int next = (current + 1) % modulo;
       if (nextIndex.compareAndSet(current, next) && current < modulo)
             return current;
    }
 }
复制代码

总结

1、Spring cloud openfeign它是一个基于feign的Http请求框架,它可以集成Sentinel或者hystrix限流熔断也可以集成Ribbon(默认)/loadbalancer负载均衡。feign支持OkHttpHttpClient(默认)去发起Http请求。

2、Spring-cloud-netfix-ribbon默认负载均衡算法为轮询

3、Spring cloud openfeign为每个服务(客户端)都创建一个Spring ApplicationContext()和当前应用ApplicationContext组成父子关系,需要的Bean都从其中获取。

4、实际上负载均衡也会和openfeign一样为每个客户端创建一个Spring ApplicationContext,设置parent为当前应用上下文。具体查看NamedContextFactory的子类

ps:上面的ApplicationContext为**AnnotationConfigApplicationContext**

问题

1、为什么需要对每个负载均衡客户端、feign客户端都创建一个ApplicationContext?

我想因该是为了配置隔离吧,父context共享公用的configuration,然后不同的子context有不同的configuration。至于问我为什么不直接从放在在对应的Client实例中,当配置中心修改配置可能需要重新创建Client(@RefreshCope支持),放在Client中无法重新创建一个client,且配置应该和client实例之间隔离。望万能的网友解答

结尾

第一次在掘金上写文章,也是最近新学的openfeign。如有错误(肯定会有错误QAQ)请在评论区留言。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享