SpringCloud负载均衡组件Ribbon源码分析

本文从源码视角简述Ribbon如何为客户端提供负载均衡能力。
一. 入口

       在《SpringCloud负载均衡组件Ribbon相关实践》中我们提到,只需要为RestTemplate增加@LoadBalanced注解,就可以为RestTemplate整合Ribbon,使其具备负载均衡的能力。那么,@LoadBalanced是如何为RestTemplate提供这种能力的呢?

下面我们先从这个注解开始说起:

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

这个类并没有什么特别有用的线索,通过在IDE工具上查看该注解的引用情况,发现如下两个类会引用该注解:

由此可以猜想,应该是通过这两个类,实现了Ribbon的功能。

除此之外,我们也可以查看所在项目的META-INF目录下的spring.factories文件,如下所示:

# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.client.CommonsClientAutoConfiguration,\
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\
org.springframework.cloud.commons.util.UtilAutoConfiguration,\
org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\
org.springframework.cloud.commons.httpclient.HttpClientConfiguration,\
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration

        同样可以看出上面提到的两个类AsyncLoadBalancerAutoConfiguration,LoadBalancerAutoConfiguration。由于AutoConfiguration引入的类都会在项目启动时被添加到Spring容器中,因此也验证了我们的猜想。

二. 跟踪分析

下面我们从LoadBalancerAutoConfiguration上开始分析:

1. LoadBalancerAutoConfiguration

扫描二维码关注公众号,回复: 8510174 查看本文章
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
     
    //1.通过@LoadBalanced注解中的@Qualifier注解,自动装载含有@LoadBalanced注解的RestTemplate
    //  添加到restTemplates中
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

    //2.创建LoadBalancerRequestFactory工厂
	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
	}

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

   //3.创建LoadBalancerInterceptor拦截器,同时注入LoadBalancerRequestFactory工厂
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

   //4.为前面的restTemplates注入LoadBalancerInterceptor拦截器
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return new RestTemplateCustomizer() {
				@Override
				public void customize(RestTemplate restTemplate) {
					List<ClientHttpRequestInterceptor> list = new ArrayList<>(
							restTemplate.getInterceptors());
					list.add(loadBalancerInterceptor);
					restTemplate.setInterceptors(list);
				}
			};
		}
	}

}

        通过对这个类的分析,可以看出其主要作用是为加了@LoadBalanced注解的RestTemplate注入LoadBalancerInterceptor拦截器。下面分析LoadBalancerInterceptor这个拦截器。分析拦截器之前,我们需要先分析RestTemplate。一般我们通过

restTemplate.getForObject("http://zz-provider-user/user/" + id, User.class)远程调用。下面分析getForObject方法。

2. RestTemplate.getForObject()

	@Override
	public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
		RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
		HttpMessageConverterExtractor<T> responseExtractor =
				new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
		return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
	}

	@Override
	public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
			ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {

		URI expanded = getUriTemplateHandler().expand(url, uriVariables);
		return doExecute(expanded, method, requestCallback, responseExtractor);
	}

	protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
			ResponseExtractor<T> responseExtractor) throws RestClientException {

		Assert.notNull(url, "'url' must not be null");
		Assert.notNull(method, "'method' must not be null");
		ClientHttpResponse response = null;
		try {
                       //1.创建请求
			ClientHttpRequest request = createRequest(url, method);
			if (requestCallback != null) {
				requestCallback.doWithRequest(request);
			}
                       //2.执行请求
			response = request.execute();
                       //3.处理请求
			handleResponse(url, method, response);
			if (responseExtractor != null) {
				return responseExtractor.extractData(response);
			}
			else {
				return null;
			}
		}
		catch (IOException ex) {
			String resource = url.toString();
			String query = url.getRawQuery();
			resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
			throw new ResourceAccessException("I/O error on " + method.name() +
					" request for \"" + resource + "\": " + ex.getMessage(), ex);
		}
		finally {
			if (response != null) {
				response.close();
			}
		}
	}

        可以看出,restTemplate发送请求的三个步骤为:创建请求,执行请求,处理请求。因为我们知道,Ribbon实现了执行请求时的负载均衡,因此我们重点分析第二步,执行请求:也就是 request.execute():

public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
    
    // 1.定义拦截器
    private final Iterator<ClientHttpRequestInterceptor> iterator;

    if (this.iterator.hasNext()) {
        ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();

        // 2.依次执行拦截器
        return nextInterceptor.intercept(request, body, this);
    }

        ------中间内容省略------

        return delegate.execute();
    }
}

        我们可以看出,在execute方法中,会先执行配置的拦截器,再执行原方法,因此可以得出结论:前面所定义的LoadBalancerInterceptor拦截器,正是在这里被注入。下面分析LoadBalancerInterceptor里面的主要工作:

3. LoadBalancerInterceptor

	@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);
		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
	}

主要会调用RibbonLoadBalancerClient.execute方法,下面继续分析。

4. RibbonLoadBalancerClient.execute()

	@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        //1.获取具体的LoadBalanced
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        
        //2.获取要调用的服务
		Server server = getServer(loadBalancer);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
				serviceId), serverIntrospector(serviceId).getMetadata(server));
        
        //3.执行
		return execute(serviceId, ribbonServer, request);
	}

        主要分析第二步,因为第二步获取服务的时候肯定需要采用负载均衡的手段来实现(不然这个注解就没用了)。下面分析getServer这个方法:

	protected Server getServer(ILoadBalancer loadBalancer) {
		if (loadBalancer == null) {
			return null;
		}
		return loadBalancer.chooseServer("default"); // TODO: better handling of key
	}

        可以看到,通过ZoneAwareLoadBalancer的chooseServer方法实现负载均衡(为什么是ZoneAwareLoadBalancer呢?因为这个类是默认实现),于是继续分析这个方法:

    public Server chooseServer(Object key) {
        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);
        }
    }

        上面的代码有点复杂,注意到判断条件:getLoadBalancerStats().getAvailableZones().size() <= 1。于是代码可以这样子理解:假设可用节点只有一个,那就直接选这一个,否则走一堆很复杂的逻辑,来选择出其中一个可用的节点。那么再继续分析chooseServer方法:

    public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
                //1.通过rule去选择
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }
    

    @Override
    public Server choose(Object key) {
        //2.获取用户配置的ILoadBalancer(这个可以自定义配置)
        ILoadBalancer lb = getLoadBalancer();
        //3.根据前面获取的ILoadBalancer,从所有服务中获取一个可用的服务节点
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }

        最终,我们看到调用了chooseRoundRobinAfterFiltering方法,它的主要作用是根据我们配置的rule,从所有server中获取一个可用的server,最终实现RestTemplate的负载均衡。其中第2步的自定义配置,可以参考《SpringCloud负载均衡组件Ribbon相关实践》。

三. 总结

最后的最后,我们做个小总结,Ribbon的实现主要内容为:

1. 用户创建了RestTemplate,并配置了@LoadBalanced注解;

2. 项目启动时,Ribbon通过LoadBalancerAutoConfiguration类,为加了@LoadBalanced注解的RestTemplate注入LoadBalancerInterceptor拦截器;

3. 当用户使用RestTemplate请求时,主要有 创建请求,执行请求,处理请求 三个步骤,其中Ribbon作用于第二步;

(1) Ribbon在RestTemplate的第二步执行请求时,会先执行配置的LoadBalancerInterceptor拦截器,再执行原方法;

(2) 当请求在LoadBalancerInterceptor拦截器中时,会根据用户自定义配置的规则,从所有服务中获取一个可用的服务,最终实现RestTemplate的负载均衡。

发布了19 篇原创文章 · 获赞 20 · 访问量 5850

猜你喜欢

转载自blog.csdn.net/qq_15898739/article/details/102808491