Ribbon负载均衡原理,Feign是如何整合Ribbon的?

  • Post author:
  • Post category:其他




1. 什么是负载均衡?

负载均衡是从多个服务中根据某个策略选择一个进行访问,常见的负载均衡分为两种


  1. 客户端负载均衡

    :即在客户端就进行负载均衡算法分配。例如spring cloud中的

    ribbon

    ,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择 一个服务器,然后进行访问

  2. 服务端负载均衡

    :在消费者和服务提供方中间使用独立的代理方式进行负载。例如

    Nginx

    ,先发送请求,然后通过

    Nginx

    的负载均衡算法,在多个服务器之间选择一 个进行访问!


常见的负载均衡算法:


  • 随机

    :通过随机选择服务进行执行,一般这种方式使用较少;

  • 轮询

    :请求来之后排队处理,轮着来

  • 加权轮询

    :通过对服务器性能的分型,给高配置,低负载的服务器分配更高的权重,均衡各个 服务器的压力;

  • 一致性hash

    :通过客户端请求的地址的HASH值取模映射进行服务器调度。

  • 最少并发

    :将请求分配到当前压力最小的服务器上



2. Ribbon的使用

Ribbon属于netflix的产品,依赖如下

<!--添加ribbon的依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

但 spring-cloud 体系下的大多数产品都整合和ribbon,如服务发现

nacos-discovery

,RPC调用

feign

组件等等,所以,使用时可以不用再引入

ribbon

依赖

使用Ribbon时只需添加

@LoadBalanced

注解即可,代表当前请求拥有了负载均衡的能力

①:为

RestTemplate

添加

@LoadBalanced

注解

@Configuration
public class RestConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    } 

②:使用

RestTemplate

进行远程调用,此次调用有负载均衡效果!

@Autowired
private RestTemplate restTemplate;

@RequestMapping(value = "/findOrderByUserId/{id}")
public R  findOrderByUserId(@PathVariable("id") Integer id) {

    String url = "http://order/findOrderByUserId/"+id;
    R result = restTemplate.getForObject(url,R.class);

    return result;
}



①:自定义负载均衡策略

自定义负载均衡策略方式有多种

  • 实现

    IRule

    接口
  • 或者继承

    AbstractLoadBalancerRule

实现基于

Nacos

权重的负载均衡策略:

nacos

中权重越大的实例请求频次越高!


//继承 AbstractLoadBalancerRule 类
@Slf4j
public class NacosRandomWithWeightRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public Server choose(Object key) {
    	//获取负载均衡器
        DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
        String serviceName = loadBalancer.getName();
        NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
        try {
            //nacos基于权重的算法
            Instance instance = namingService.selectOneHealthyInstance(serviceName);
            return new NacosServer(instance);
        } catch (NacosException e) {
            log.error("获取服务实例异常:{}", e.getMessage());
            e.printStackTrace();
        }
        return null;
    }
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

自定义负载均衡策略的配置也有两种


  • 全局配置

    :当前服务调用其他微服务时,一律使用指定的负载均衡算法

    @Configuration
    public class RibbonConfig {
    
        /**
         * 全局配置
         * 指定负载均衡策略
         * @return
         */
        @Bean
        public IRule ribbonRule() {
            // 指定使用基于`Nacos`权重的负载均衡策略:`nacos`中权重越大的实例请求频次越高!
            return new NacosRandomWithWeightRule();
        }
    

  • 局部配置

    :当前服务调用指定的微服务时,使用对应的负载均衡算法,比如调用

    order

    服务使用该算法,调用其他的不使用!

    # 被调用的微服务名
    mall-order:
      ribbon:
        # 自定义的负载均衡策略(基于随机&权重)
        NFLoadBalancerRuleClassName: com.china.test.ribbondemo.rule.NacosRandomWithWeightRule
    



②:Ribbon的饥饿加载


Ribbon

默认懒加载,意味着只有在发起调用的时候才会创建客户端。在第一次进行服务调用时会做一些初始化工作,比如:创建负载均衡器 ,如果网络情况不好,这次调用可能会超时。

在这里插入图片描述

可以开启饥饿加载,在项目启动时就完成初始化工作,解决第一次调用慢的问题

ribbon:
  eager-load:
    # 开启ribbon饥饿加载,源码对应属性配置类:RibbonEagerLoadProperties
    enabled: true
    # 配置mall-user使用ribbon饥饿加载,多个使用逗号分隔
    clients: mall-order

开启之后,可以看到,第一次调用日志已经没有初始化工作了

在这里插入图片描述



3. Ribbon的负载均衡原理



①:收集带有@LoadBalanced注解的RestTemplate,并为其添加一个负载均衡拦截器

上面的使用案例中,如果不加

@LoadBalanced

注解的话,

RestTemplate

没有负载均衡功能的,为什么一个

@LoadBalanced

注解就使

RestTemplate

具有负载均衡功能了呢?下面来看一下Ribbon的负载均衡原理

Ribbon既然在springboot中使用,自然会想到springboot对Ribbon的自动配置类

RibbonAutoConfiguration

!这个自动配置类被加载的前置条件是:需要加载

LoadBalancerAutoConfiguration

类,如下所示

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")

//加载的前置条件:先加载 LoadBalancerAutoConfiguration类
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
		AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
		ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {



LoadBalancerAutoConfiguration

类是属于spring-cloud-common包下的,该包是spring cloud的基础包,肯定会被加载的

在这里插入图片描述

进入

LoadBalancerAutoConfiguration

类中,该类中注册了几个bean,主要做了以下几件事

  1. 收集到所有带有

    @LoadBalanced

    注解的

    RestTemplate

    ,并放入

    restTemplates

    集合
  2. 创建一个带有负载均衡功能的拦截器

    LoadBalancerInterceptor
  3. 在容器类初始化完毕后,把所有的

    RestTemplate

    内部都添加上拦截器

    LoadBalancerInterceptor

    ,当有请求经过

    ribbon

    ,通过

    restTemplate

    发起调用时,会先走此拦截器,实现负载均衡
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	//Springboot会将所有带有@LoadBalanced注解的RestTemplate,都放进restTemplates这个集合中去
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

	//SmartInitializingSingleton :该方法会等待所有类都初始化完毕后执行
	// 拿到上面收集到的所有带有@LoadBalanced注解的RestTemplate
	// 执行下面的函数式接口方法customize,把拦截器 放入每一个restTemplate中,
	//当有请求经过ribbon,通过 restTemplate 发起调用时,会先走此拦截器,实现负载均衡
	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
				for (RestTemplateCustomizer customizer : customizers) {
					customizer.customize(restTemplate);
				}
			}
		});
	}

	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
	}

	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {

		//向容器中放入一个带有负载均衡功能的拦截器
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		//这是一个函数式接口,此处实现的是接口的customize方法,定义了一个操作,操作内容如下:
		// 传入一个restTemplate,并把上面的拦截器 放入restTemplate中
		//当有请求经过ribbon,通过 restTemplate 发起调用时
		//会先走此拦截器,实现负载均衡
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}


LoadBalancerAutoConfiguration

是如何精准的收集到所有的带有

@LoadBalanced

注解的

RestTemplate

呢?点开

@LoadBalanced

注解,发现他是带有

@Qualifier

限定符的!

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier  //spring的限定符
public @interface LoadBalanced {

}


@Qualifier

限定符的作用:

  • 一个接口有两个实现类,当我们通过

    @AutoWired

    注解进行注入时,

    spring

    不知道应该绑定哪个实现类,从而导致报错。
  • 这时就可以通过

    @Qualifier

    注解来解决。通过它可以标识我们需要的实现类。而

    @LoadBalanced

    的元注解是

    @Qualifier

    ,所以 源码中就可以通过

    @LoadBalanced

    注解来限定收集所有带有

    @LoadBalanced

    注解的

    RestTemplate

    实现



②:选择负载均衡器,执行负载均衡算法(默认轮询)

上面说到请求经过ribbon的

RestTemplate

调用时,会先走其内部的拦截器

LoadBalancerInterceptor

的负载均衡逻辑。既然是走拦截器,那么就可以去看

LoadBalancerInterceptor



intercept()

方法,一般该方法就有负载均衡逻辑!

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
		//负载均衡器的 execute 方法
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		//获取负载均衡器 ILoadBalancer 
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		// 通过负载均衡选择一个服务器
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
		//向某一台服务器 发起HTTP请求
		return execute(serviceId, ribbonServer, request);
	}

如上,

Ribbon

通过负载均衡 选择一台机器 发起

http

请求已经执行完毕!



Ribbon负载均衡器的默认实现:ZoneAwareLoadBalancer

上面说到

getLoadBalancer(serviceId)

方法可以获取一个负载均衡器,用于执行负载均衡算法,这个负载均衡器已经在

RibbonClientConfiguration

配置类中初始化好了,获取时直接从容器中取即可

	@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
		// 从配置类中找一个负载均衡器 ILoadBalancer
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}
		// 如果没有就创建一个负载均衡器的实现 ZoneAwareLoadBalancer
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}

可以看到

RibbonClientConfiguration

配置类中,默认初始化的是

ZoneAwareLoadBalancer

,它具备区域感知的能力。

在这里插入图片描述

在创建默认负载均衡器时(

new ZoneAwareLoadBalancer

)做了什么呢?



  1. nacos

    注册中心上立即获取最新的服务信息,保存在

    ribbon

    的本地服务列表中
  2. 使用延时定时线程池,定时从nacos上拉取最新服务地址,更新

    ribbon

    的本地服务列表中

进入

new ZoneAwareLoadBalancer

中:

    void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
        this.setEnablePrimingConnections(false);
        
        //开启定时延时任务(会延后执行),定时从nacos上拉取最新服务地址,更新`ribbon`的本地服务列表中
        enableAndInitLearnNewServersFeature();
        
		//进入后立即执行,从`nacos`注册中心上立即获取最新的服务信息,保存在`ribbon`的本地服务列表中
        updateListOfServers();
        
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }

开启定时任务方法

enableAndInitLearnNewServersFeature();

如下:

    @Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
        	//开启一个线程,执行更新任务
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                    	//UpdateAction 又是一个函数式接口,
                    	//doUpdate方法需要看一下传进来的方法内容,下文展示
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };
            
			//开启定时延时任务,定时执行上面的线程
            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
            );
        } else {
            logger.info("Already active, no-op");
        }
    }

================== 函数式接口的 updateAction.doUpdate()方法内容如下=============

    protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate() {
            updateListOfServers();
        }
    };

	// updateListOfServers方法如下:
    @VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
        	// 该方法会从对应的配置中心中取最新数据
            servers = serverListImpl.getUpdatedListOfServers();
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);

            if (filter != null) {
                servers = filter.getFilteredListOfServers(servers);
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
            }
        }
        // 更新本地服务列表!
        updateAllServerList(servers);
    }

负载均衡器初始化时,立即从注册中心获取最新服务的方法

updateListOfServers()

,如下:

    @VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
        	//从注册中心上获取服务地址
            servers = serverListImpl.getUpdatedListOfServers();
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);

            if (filter != null) {
                servers = filter.getFilteredListOfServers(servers);
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
            }
        }
        //更新本地服务列表!
        updateAllServerList(servers);
    }

在这里插入图片描述



Ribbon负载均衡算法的默认实现:ZoneAvoidanceRule

有了负载均衡器

ZoneAwareLoadBalancer

,接下来执行负载均衡算法即可

getServer(loadBalancer, hint);

,回顾上边第②条的代码:

	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		//获取负载均衡器 ILoadBalancer 
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		// 通过负载均衡算法 选择一个服务器地址
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
		//向某一台服务器 发起HTTP请求
		return execute(serviceId, ribbonServer, request);
	}

进入负载均衡算法选择服务器的方法

getServer(loadBalancer, hint)

    public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
            	//这个rule就是ribbon的负载均衡算法!
            	//默认是 ZoneAvoidanceRule ,在没有区域的环境下,类似于轮询(RandomRule)
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }


ZoneAvoidanceRule

的核心逻辑如下:使用

cas

+

死循环

轮询服务器地址

    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextIndex.get();
            //取模得到其中一个
            int next = (current + 1) % modulo;
            //cas赋值 ,返回nextIndex的机器
            if (nextIndex.compareAndSet(current, next) && current < modulo)
                return current;
        }
    }

其中,

ZoneAvoidanceRule

的初始化和负载均衡器

ZoneAwareLoadBalancer

的初始化一样,也在

RibbonClientConfiguration

配置类中完成!

	@Bean
	@ConditionalOnMissingBean
	public IRule ribbonRule(IClientConfig config) {
		if (this.propertiesFactory.isSet(IRule.class, name)) {
			return this.propertiesFactory.get(IRule.class, config, name);
		}
		// 负载均衡策略的 默认实现ZoneAvoidanceRule 
		ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
		rule.initWithNiwsConfig(config);
		return rule;
	}

Ribbon的负载均衡策略有如下几种


  • RandomRule

    :随机策略, 随机选择一个Server。

  • RetryRule

    : 重试策略 。对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server。

  • RoundRobinRule

    : 轮询策略 。 轮询index,选择index对应位置的Server。

  • AvailabilityFilteringRule

    :可用性过滤策略 。 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就是检查status里记录的各个Server的运行状态。

  • BestAvailableRule

    :最低并发策略 。 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。

  • WeightedResponseTimeRule

    :响应时间加权重策略。根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低。

  • ZoneAvoidanceRule

    :区域权重策略。

    默认的负载均衡策略

    ,综合判断server所在区域的性能和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server。在没有区域的环境下,类似于轮询(

    RandomRule

    )

  • NacosRule

    : 同集群优先调用



4. Feign的原理


Feign和OpenFeign的区别?


  • Feign

    :Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可帮助我们更加便捷、优雅地调用HTTP API。可以单独使用

  • OpenFeign

    :Spring Cloud openfeign对Feign进行了 增强,使其支持

    Spring MVC

    注解,另外还整合了

    Ribbon



    Eureka

    ,从而使得Feign的使用更加方便


Feign的调用原理图

(可在每一层做扩展)

在这里插入图片描述


OpenFeign的常用配置项:

(对应上图,可以在配置中做扩展)


  • 日志配置

    :有时候我们遇到 Bug,比如接口调用失败、参数没收到等问题,或者想看看调用性能,就需要配置 Feign 的 日志了,以此让 Feign 把请求信息输出来。日志配置分为局部配置和全局配置!

  • 拦截器配置

    :每次 feign 发起http调用之前,会去执行拦截器中的逻辑,就类似mvc中的拦截器。比如:做权限认证。

  • 超时时间配置

    :通过 Options 可以配置连接超时时间(默认2秒)和读取超时时间(默认5秒),注意:Feign的底层用的是

    Ribbon

    ,但超时时间以Feign配置为准

  • 客户端组件配置

    :Feign 中默认使用 JDK 原生的

    URLConnection

    发送 HTTP 请求,我们可以集成别的组件来替换掉 URLConnection,比如 Apache HttpClient,OkHttp。

  • GZIP 压缩配置

    :再配置文件中开启压缩可以有效节约网络资源,提升接口性能

  • 编码器解码器配置

    :Feign 中提供了自定义的编码解码器设置,同时也提供了多种编码器的实现,比如 Gson、Jaxb、Jackson。 我们可以用不同的编码解码器来处理数据的传输。如果你想传输 XML 格式的数据,可以自定义 XML 编码解 码器来实现获取使用官方提供的 Jaxb



5. OpenFeign是如何整合Ribbon的?

通过上边,我们已经知道

Ribbon

可以把微服务的 服务名 通过负载均衡策略替换成某一台机器的

IP

地址,然后通过

http

请求进行访问!如下所示:

http://

mall-order

/order/findOrderByUserId ====> http://

192.168.100.15

/order/findOrderByUserId



Feign

则是把参数组装到url中去,实现一个完整的RPC调用

http://mall-order/order/findOrderByUserId/

5(参数)

====> http://192.168.100.15/order/findOrderByUserId/

5(参数)



①:扫描所有@FeignClient注解,以FactoryBean的形式注册到容器中

Feign的使用需要用到

@EnableFeignClients



@FeignClient("gulimall-ware")

这两个注解,其中,

@EnableFeignClients

通过

@Import

向容器中添加了一个bean定义注册器,用于扫描

@FeignClient("gulimall-ware")

注解,注册bean定义

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//通过`@Import`向容器中添加了一个bean定义注册器 FeignClientsRegistrar
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

}

进入

FeignClientsRegistrar



registerBeanDefinitions

方法,查看具体注册了什么

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		//注册一下默认配置
		registerDefaultConfiguration(metadata, registry);
		//注册所有@FeignClient注解 标注的类
		registerFeignClients(metadata, registry);
	}

注册逻辑中,最主要的就是把所有

@FeignClient

注解 标注的类以

FactoryBean

的形式注册到容器中

	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		// 1. 获取一个扫描器
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		//2. 拿到所有@FeignClient注解标注的类
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}

		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
					// 3. 把这些类注入容器
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

修改bean定义为

FactoryBean

的子类

FeignClientFactoryBean

	private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		//把bean定义构建成一个FactoryBean
		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);
		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();

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

		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);
	}



②:RPC调用时,通过LoadBalancerFeignClient整合Ribbon,实现负载均衡调用

既然是把

@FeignClient("gulimall-ware")

注解标注的类 以

FactoryBean

的子类

FeignClientFactoryBean

的形式注入到容器,那么RPC调用时肯定是通过调用

FeignClientFactoryBean



getObject

方法来使用的!

	@Override
	public Object getObject() throws Exception {
		return getTarget();
	}


getTarget()

	<T> T getTarget() {
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}


loadBalance()

	protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		//获取的其实是LoadBalanceFeignClient,用于整合Ribbon的负载均衡
		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?");
	}


Client client = getOptional(context, Client.class)

;获取的是

Feign

的客户端实现:

在这里插入图片描述


整合Ribbon逻辑

:进入

LoadBalancerFeignClient



execute

方法中,使用

Feign

的负载均衡器向

Ribbon

发请求,已达到整合

Ribbon

的目的!

org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient # execute

	@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		try {
			URI asUri = URI.create(request.url());
			String clientName = asUri.getHost();
			URI uriWithoutHost = cleanUrl(request.url(), clientName);
			//使用Feign的负载均衡器向Ribbon发请求,已达到整合Ribbon的目的!
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);

			IClientConfig requestConfig = getClientConfig(options, clientName);
			return lbClient(clientName)
					.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
		}
		catch (ClientException e) {
			IOException io = findIOException(e);
			if (io != null) {
				throw io;
			}
			throw new RuntimeException(e);
		}
	}

综上所述,负载均衡的逻辑还是在

Ribbon

中,而

Feign

通过整合

Ribbon

实现了带有负载均衡的

RPC

调用!



6. 总结图

在这里插入图片描述



版权声明:本文为qq_45076180原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。