Ribbon(三) Ribbon源码分析-负载均衡是如何通过@LoadBalanced 自动配置上去的

本文详细介绍了Spring Cloud Ribbon的负载均衡原理,它是一个基于HTTP和TCP的客户端负载均衡工具,通过@LoadBalanced注解启用。Ribbon实现了LoadBalancerClient接口,通过ILoadBalancer选择服务实例并执行请求。负载均衡策略默认为轮询,可以通过ZoneAwareLoadBalancer实现区域感知的负载均衡。此外,LoadBalancerAutoConfiguration类提供了Ribbon的自动化配置,包括创建LoadBalancerInterceptor和RestTemplateCustomizer,实现客户端负载均衡的拦截和处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    以下源码分析只针对:

    

  <spring.cloud.version>Finchley.SR4</spring.cloud.version>
  <spring.boot.version>2.0.9.RELEASE</spring.boot.version>

  

    Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。

   Ribbon的负载均衡的引入是从以下代码开始的:

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

那我们从这个@LoadBalanced注解开始,打开它的源码: 

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient.
   标记RestTemplate的注解以使用LoadBalancerClient对象.
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {

}

      从注释中可以知道,这个注解是用来给RestTemplate做标记,以使用负载均衡客户端(LoadBalancerClient)来配置它。所以,我们在生成的RestTemplate的bean上添加这么一个注解,这个bean就会配置LoadBalancerClient。

    接下来再查看 LoadBalancerClient 源码,它位于 spring-cloud-commons-xxx.jar 中的org.springframework.cloud.client.loadbalancer包下.   


public interface LoadBalancerClient extends ServiceInstanceChooser {
	
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
	
	<T> T execute(String serviceId, ServiceInstance serviceInstance,
			LoadBalancerRequest<T> request) throws IOException;
	
	URI reconstructURI(ServiceInstance instance, URI original);
}

LoadBalancerClient是一个接口,里面有三个方法(包括从 ServiceInstanceChooser中继承的choose() 方法) : 

  • ServiceInstance choose(String serviceId);从方法名上就可以看出,是根据传入的serviceId(服务名),从负载均衡器中选择一个服务实例ServiceInstance类 , 这个方法是继承自它的父接口 ServiceInstanceChooser接口的. 
  • execute方法,使用从负载均衡器中选择的服务实例来执行请求内容。 
  • URI reconstructURI(ServiceInstance instance, URI original);方法,是重新构建一个URI的,还记得我们在代码中,通过RestTemplate请求服务时,写的是服务名(restTemplate.getForObject("http://MICROSERVICE-PROVIDER-PRODUCT/product/get/"+id, Product.class); )吧,这个方法就会把这个请求的URI进行转换,将服务名替换成这个服务下的一个实例名,返回  实例host+port,通过  实例host+port的形式去请求服务实例 .

这个接口的层次关系为: 

     LoadBalancerClient的在Ribbon中的实现类为RibbonLoadBalancerClient,这个类是非常重要的一个类,最终的负载均衡的请求处理,由它来执行。它位于:  请求的负载均衡在execute中实现:

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
        //每次发送请求都会获取一个ILoadBalancer ,会涉及负载均衡(IRULS),服务器列表集群(ServerList) 和检验服务是否存活(IPing)等细节实现
		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));

		return execute(serviceId, ribbonServer, request);
}

     追踪      getLoadBalancer() 可知它会得到一个 ILoadBalancer对象. 

    而 getServer() 则直接调用了ILoadBalancer 的chooseServer方法来使用负载均衡策略,从已知的服务列表中选出一个服务器实例 . 

protected Server getServer(ILoadBalancer loadBalancer) {
		if (loadBalancer == null) {
			return null;
		}
		return loadBalancer.chooseServer("default"); 
	}

  那么 ILoadBalancer 是如何实现的呢? 我们可以看一个它的实现类  ZoneAwareLoadBalancer 类(它也是默认采用的负载均衡策略,它是一个基于区域感知的轮询策略),注意它的构造方法:

  public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
                                 IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
                                 ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }

它的参数分析: 

   IClientConfig      client的配置类,具体指的DefaultClientConfigImpl
   IRule     负载均衡的策略类,默认轮询 RoundRobinRule
   IPing      服务可用性检查,默认DummyPing
   ServerList     服务列表获取,ConfigurationBasedServerList
   ServerListFilter      服务列表过滤 ZonePreferenceServerListFilter

    ZoneAwareLoadBalancer其中一个重要的方法就是com.netflix.loadbalancer.ZoneAwareLoadBalancer#chooseServer. 

@Override
public Server chooseServer(Object key) {
     //只有当负载均衡器中维护的实例所属的Zone区域的个数大于1的时候才会执行选择策略, 否则还是使用父类的实现. 
        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();
         //为当前负载均衡器中的所有Zone区域分别创建快照,保存在zoneSnapshot中,这些快照中的数据用于后续的算法
            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);
            }
        //根据两个阈值来获得可用Zone区域的集合,getAvailableZones会通过zoneSnapshot实现可用区域挑选
            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);
                    //选择具体的服务实例
                    //在chooseServer中将会使用IRule接口的choose函数来选择具体服务实例。在这里,IRule接口的实现会实现ZoneAvoidanceRule来挑选具体的服务实例。
                    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);  //这里调用的是父类com.netflix.loadbalancer.BaseLoadBalancer的chooseServer方法
        }
    }

com.netflix.loadbalancer.BaseLoadBalancer的chooseServer方法分析: 
       

public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
                 //根据具体的路由算法获取服务
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }

那么这个  rule 又是什么呢,追踪一下源码可知: 

protected IRule rule = DEFAULT_RULE;

//而  DEFAULT_RULE 又是
 private final static IRule DEFAULT_RULE = new RoundRobinRule();

这就可知系统默认的负载均衡策略是  RoundRobin . 具体的负载均衡算法的实现,请看:    Ribbon(五) Ribbon源码分析 - IRULE负载均衡的实现

  ===================================================================================

     让我们再回到前面LoadBalancerClient类.   在LoadBalancerClient接口的同一个包路径下,还会看到另一个LoadBalancerAutoConfiguration类,看名字就感觉这是一个自动配置LoadBalancer的. 它的源码如下: 

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

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

    ....
}

整理这个LoadBalancerAutoConfiguration可以得到如下关系:

详细分析一下LoadBalancerAutoConfiguration可知,Ribbon实现负载均衡自动化配置需要满足下面两个条件: 

  • @ConditionalOnClass(RestTemplate.class):RestTemplate必须存在于当前工程的环境中。
  • @ConditionalOnBean(LoadBalancerClient.class):在Spring的Bean工程中必须有LoadBalancerClient的实现bean。

LoadBalancerAutoConfiguration类的其它代码(主要看@Bean修饰的代码,可以看出它还主要做了下面三件事:

 /**
     * 创建SmartInitializingSingleton接口的实现类。Spring会在所有
     * 单例Bean初始化完成后回调该实现类的afterSingletonsInstantiated()
     * 方法。在这个方法中会为所有被@LoadBalanced注解标识的
     * RestTemplate添加ribbon的自定义拦截器LoadBalancerInterceptor。
     */
@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);
	}

    /**
     * 创建Ribbon自定义拦截器LoadBalancerInterceptor
     * 创建前提是当前classpath下不存在spring-retry。
     * 所以LoadBalancerInterceptor是默认的Ribbon拦截
     * 请求的拦截器。
     */
	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {

		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}
        /**
       * 添加拦截器具体方法。首先获取当前拦截器集合(List)
       * 然后将loadBalancerInterceptor添加到当前集合中
       * 最后将新的集合放回到restTemplate中。
       */
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}

	/**
	 * Auto configuration for retry mechanism.
	 */
	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryAutoConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public LoadBalancedRetryFactory loadBalancedRetryFactory() {
			return new LoadBalancedRetryFactory() {
			};
		}

	}

	/**
	 * Auto configuration for retry intercepting mechanism.
	 */
	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryInterceptorAutoConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public RetryLoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRetryProperties properties,
				LoadBalancerRequestFactory requestFactory,
				LoadBalancedRetryFactory loadBalancedRetryFactory) {
			return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
					requestFactory, loadBalancedRetryFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}
  •   创建了一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。
  •    创建了一个RestTemplateCustomizer的Bean,用于给RestTemplate增加LoadbalancerInterceptor
  •     维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。

小结: 回头看一下我们自己实现的项目,就定义了RestTemplate的一个对象,并且引入了spring-cloud相关的包,存在RibbonLoadBalancerClient作为LoadBalancerClient的实现类,所以,满足自动化配置的条件。

=================================================================================================



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangyingchengqi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值