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

@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的負載均衡。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章