springcloud ribbon @LoadBalance负载均衡源码流程分析

一、编写示例

   1.服务端

    pom.xml

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>0.2.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    2.提供者代码,就是一个普通的HTTP REST接口。

@SpringBootApplication
@EnableDiscoveryClient
@RestController
@Slf4j
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
    @GetMapping("provider")
    public String provider(){
        log.info("..................................");
        return "hello,provider";
    }
}

3.消费端

POM


    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.retry</groupId>
                    <artifactId>spring-retry</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.springframework.retry</groupId>-->
<!--            <artifactId>spring-retry</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--<dependency>-->
            <!--<groupId>org.springframework.retry</groupId>-->
            <!--<artifactId>spring-retry</artifactId>-->
        <!--</dependency>-->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>0.2.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

这里注意,如果不想要RetryTemplate,就去掉Retry的依赖,否则默认会引入的。

消费端代码

@SpringBootApplication
@Slf4j
@EnableDiscoveryClient
@RestController
public class ConsumeApplication {

    @Autowired
    RestTemplate restTemplate;

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        clientHttpRequestFactory.setConnectTimeout(5 * 1000);
        clientHttpRequestFactory.setReadTimeout(5 * 1000);
        return new RestTemplate(clientHttpRequestFactory);
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumeApplication.class, args);
    }
    @GetMapping("test")
    public String getContent(){
        log.info("发起请求");
        return restTemplate.getForObject("http://provider/provider",String.class);
    }

}

二、初始化自动配置代码分析

1.org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,这个为RIBBON私有的
LoadBalancerClient,LoadBalancer,IRule对象的初始化。
@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
	@Bean
	public SpringClientFactory springClientFactory() {
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}

	@Bean
	@ConditionalOnMissingBean(LoadBalancerClient.class)
	public LoadBalancerClient loadBalancerClient() {
		return new RibbonLoadBalancerClient(springClientFactory());
	}

	@Bean
	@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
	@ConditionalOnMissingBean
	public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) {
		return new RibbonLoadBalancedRetryFactory(clientFactory);
	}

2.org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration为springcloud-common的对象自动初始化。 

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

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

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

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

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

	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

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

 这里的 restTemplate只会取加了@LoadBalance注解的,因为@LoadBalance加上了@Qualifer,这个注解会有筛选过滤作用。

去除了RetryTemplate就会走默认,否则走重试balance.

1.首先创建LoadBalancerRequestFactory工厂对象。

 2.创建LoadBalancerInterceptor对象

3.对RestTemplate列表对象,设置拦截器属性。

三、调用分析

  1.RestTemplate.getObject方法开始。这个方法会调用doExecute

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {	
    @Nullable
	protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
			@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {

		Assert.notNull(url, "URI is required");
		Assert.notNull(method, "HttpMethod is required");
		ClientHttpResponse response = null;
		try {
			ClientHttpRequest request = createRequest(url, method);
			if (requestCallback != null) {
				requestCallback.doWithRequest(request);
			}
			response = request.execute();
			handleResponse(url, method, response);
			return (responseExtractor != null ? responseExtractor.extractData(response) : 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();
			}
		}
	}

 

2.createRequest方法,RestTemplate是继承于InterceptingHttpAccessor和HttpAccessor

	HttpAccessor
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
		ClientHttpRequest request = getRequestFactory().createRequest(url, method);
		if (logger.isDebugEnabled()) {
			logger.debug("Created " + method.name() + " request for \"" + url + "\"");
		}
		return request;
	}

 3.getRequestFactory方法又是来源于InterceptingHttpAccessor,这里会对原有httpRequestFactory工厂包装。

public ClientHttpRequestFactory getRequestFactory() {
		List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
		if (!CollectionUtils.isEmpty(interceptors)) {
			ClientHttpRequestFactory factory = this.interceptingRequestFactory;
			if (factory == null) {
				factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
				this.interceptingRequestFactory = factory;
			}
			return factory;
		}
		else {
			return super.getRequestFactory();
		}
	}

public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {

	private final List<ClientHttpRequestInterceptor> interceptors;


	/**
	 * Create a new instance of the {@code InterceptingClientHttpRequestFactory} with the given parameters.
	 * @param requestFactory the request factory to wrap
	 * @param interceptors the interceptors that are to be applied (can be {@code null})
	 */
	public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory,
			@Nullable List<ClientHttpRequestInterceptor> interceptors) {

		super(requestFactory);
		this.interceptors = (interceptors != null ? interceptors : Collections.emptyList());
	}


	@Override
	protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
		return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
	}

}

 所以最后的ClientHttpRequest为InterceptingClientHttpRequest

 3.接着我们回到第一步,response = request.execute();因为已经创建好request,现在开始执行。

class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest {

	private final ClientHttpRequestFactory requestFactory;

	private final List<ClientHttpRequestInterceptor> interceptors;

	private HttpMethod method;

	private URI uri;


	protected InterceptingClientHttpRequest(ClientHttpRequestFactory requestFactory,
			List<ClientHttpRequestInterceptor> interceptors, URI uri, HttpMethod method) {

		this.requestFactory = requestFactory;
		this.interceptors = interceptors;
		this.method = method;
		this.uri = uri;
	}


	@Override
	public HttpMethod getMethod() {
		return this.method;
	}

	@Override
	public String getMethodValue() {
		return this.method.name();
	}

	@Override
	public URI getURI() {
		return this.uri;
	}

	@Override
	protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
		InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
		return requestExecution.execute(this, bufferedOutput);
	}


	private class InterceptingRequestExecution implements ClientHttpRequestExecution {

		private final Iterator<ClientHttpRequestInterceptor> iterator;

		public InterceptingRequestExecution() {
			this.iterator = interceptors.iterator();
		}

		@Override
		public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
			if (this.iterator.hasNext()) {
				ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
				return nextInterceptor.intercept(request, body, this);
			}
			else {
				HttpMethod method = request.getMethod();
				Assert.state(method != null, "No standard HTTP method");
				ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
				request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
				if (body.length > 0) {
					if (delegate instanceof StreamingHttpOutputMessage) {
						StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
						streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
					}
					else {
						StreamUtils.copy(body, delegate.getBody());
					}
				}
				return delegate.execute();
			}
		}
	}

}

 InterceptingClientHttpRequest的execute会执行executeInternel方法,最后调用

InterceptingRequestExecution.execute

 InterceptingRequestExecution.execute这个方法先会调用各种拦截器进行处理,处理完后,再会由拦截器重新调用这个对象的execute方法,做成责任链模式。最后调用缺省的HTTP代理请求工厂生成代理对象真正发起HTTP请求。

目前的拦截器就是从注册中心获取实例地址,替换URL.

4.接着进行LoadBalancerInterceptor.interropt方法。

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;
	private LoadBalancerRequestFactory requestFactory;

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
		this.loadBalancer = loadBalancer;
		this.requestFactory = requestFactory;
	}

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
		// for backwards compatibility
		this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
	}

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
	}
}

 5.这里的loadBalancer就是RibbonLoadBalancerClient,所以调用这个进行URL替换。

public class RibbonLoadBalancerClient implements LoadBalancerClient {

	private SpringClientFactory clientFactory;

	public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
		this.clientFactory = clientFactory;
	}

	@Override
	public URI reconstructURI(ServiceInstance instance, URI original) {
		Assert.notNull(instance, "instance can not be null");
		String serviceId = instance.getServiceId();
		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);

		URI uri;
		Server server;
		if (instance instanceof RibbonServer) {
			RibbonServer ribbonServer = (RibbonServer) instance;
			server = ribbonServer.getServer();
			uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
		} else {
			server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
			IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
			ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
			uri = updateToSecureConnectionIfNeeded(original, clientConfig,
					serverIntrospector, server);
		}
		return context.reconstructURIWithServer(server, uri);
	}

	@Override
	public ServiceInstance choose(String serviceId) {
		Server server = getServer(serviceId);
		if (server == null) {
			return null;
		}
		return new RibbonServer(serviceId, server, isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
	}

	@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		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));

		return execute(serviceId, ribbonServer, request);
	}

6.接着调用RibbonLoadBalancerClient.getLoadBalancer获取负载均衡器。

 7.SpringClientFactory.getLoadBalancer

return getInstance(name, ILoadBalancer.class);这个会去容器工厂自动创建获取ILoadBalancer类。 

 8.ribbonLoadBalancingHttpClient为实现类。

	package org.springframework.cloud.netflix.ribbon.apache;


/**
 * @author Spencer Gibb
 */
@Configuration
@ConditionalOnClass(name = "org.apache.http.client.HttpClient")
@ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true)
public class HttpClientRibbonConfiguration {
@Bean
	@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
	@ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate")
	public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient(
		IClientConfig config, ServerIntrospector serverIntrospector,
		ILoadBalancer loadBalancer, RetryHandler retryHandler, CloseableHttpClient httpClient) {
		RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(httpClient, config, serverIntrospector);
		client.setLoadBalancer(loadBalancer);
		client.setRetryHandler(retryHandler);
		Monitors.registerObject("Client_" + this.name, client);
		return client;
	}

9.这个依赖于ribbonLoadBalancer,实际为这个类ZoneAwareLoadBalancer

package org.springframework.cloud.netflix.ribbon;


/**
 * @author Dave Syer
 * @author Tim Ysewyn
 */
@SuppressWarnings("deprecation")
@Configuration
@EnableConfigurationProperties
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {	
@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}

 10.上面那个类又依赖于IRULE,所以最后创建 ribbonRule,实现类为

ZoneAvoidanceRule

 

 11.创建完RULE后,创建loadBalance

12.创建ribbonLoadBalancer

 13.我们再回到第5步,    @Override
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        Server server = getServer(loadBalancer);,开始获取server

 

 

 

 14.选择好server后开始执行。

 

 15.回到LoadBalancerRequestFactory.apply

 16.又回到InterceptingRequestExecution.execute

 17.接下来开始替换URL

 18。RibbonLoadBalancerClient.reconstructURI

 

 19.

LoadBalancerContext.reconstructURIWithServer

 20.url替换完毕,调用默认的HTTP代理对象发起请求,返回

 

21 请求结束,处理消息体转换,返回数据。 

21.请求完毕。

四、服务实例获取和选择分析

  1.我们先回到第三单,第9步,这个依赖于ribbonLoadBalancer,实际为这个类ZoneAwareLoadBalancer,这个为负载均衡器的选择服务实例逻辑。

2.ZoneAwareLoadBalancer主要为感知分区的实例选择,我们一般用不到。目前使用DynamicServerListLoadBalancer

AbstractLoadBalancer

AbstractLoadBalancer类的定义如下:

public abstract class AbstractLoadBalancer implements ILoadBalancer {
    
    public enum ServerGroup{
        ALL,
        STATUS_UP,
        STATUS_NOT_UP        
    }
    public Server chooseServer() {
    	return chooseServer(null);
    }
    public abstract List<Server> getServerList(ServerGroup serverGroup);
    public abstract LoadBalancerStats getLoadBalancerStats();    
}

关于这个类我说以下几点:
1. AbstractLoadBalancer实现了ILoadBalancer接口,但它是一个抽象类,它里边定义了一个关于服务实例的分组枚举类,包含了三种类型的服务:ALL表示所有服务,STATUS_UP表示正常运行的服务,STATUS_NOT_UP表示下线的服务。
2. chooseServer方法毫无疑问是用来选取一个服务实例,但是要怎么选这里并没有说,我们以后在它的实现类里边寻找选取策略。
3. getServerList方法用来获取某一个分组中所有的的服务实例。
4. getLoadBalancerStats方法用来获取LoadBalancerStats对象,LoadBalancerStats对象中保存了每一个服务的所有细节信息。

BaseLoadBalancer

BaseLoadBalancer是AbstractLoadBalancer的一个实现类,源码比较长我就不贴出来的,我们在这里主要来说说BaseLoadBalancer提供了哪些功能:

1. 首先这个类中有两个List集合中放的Server对象,一个List集合用来保存所有的服务实例,还有一个List集合用来保存当前有效的服务实例。
2. BaseLoadBalancer中定义了一个IPingStrategy,用来描述服务检查策略,IPingStrategy默认实现采用了SerialPingStrategy实现,SerialPingStrategy中的pingServers方法就是遍历所有的服务实例,一个一个发送请求,查看这些服务实例是否还有效,如果网络环境不好的话,这种检查策略效率会很低,如果我们想自定义检查策略的话,可以重写SerialPingStrategy的pingServers方法。
3. 在BaseLoadBalancer的chooseServer方法中(负载均衡的核心方法),我们发现最终调用了IRule中的choose方法来找到一个具体的服务实例,IRule是一个接口,在BaseLoadBalancer它的默认实现是RoundRobinRule类,RoundRobinRule类中采用了最常用的线性负载均衡规则,也就是所有有效的服务端轮流调用。
4. 在BaseLoadBalancer的构造方法中会启动一个PingTask,这个PingTask用来检查Server是否有效,PingTask的默认执行时间间隔为10秒。
5. markServerDown方法用来标记一个服务是否有效,标记方式为调用Server对象的setAlive方法设置isAliveFlag属性为false。
6. getReachableServers方法用来获取所有有效的服务实例列表。
7. getAllServers方法用来获取所有服务的实例列表。
8. addServers方法表示向负载均衡器中添加一个新的服务实例列表。

BaseLoadBalancer的功能大概就这么多。

DynamicServerListLoadBalancer

DynamicServerListLoadBalancer是BaseLoadBalancer的一个子类,在DynamicServerListLoadBalancer中对基础负载均衡器的功能做了进一步的扩展,我们来看看。

1. 首先DynamicServerListLoadBalancer类一开始就声明了一个变量serverListImpl,serverListImpl变量的类型是一个ServerList<T extends Server>,这里的泛型得是Server的子类,ServerList是一个接口,里边定义了两个方法:一个getInitialListOfServers用来获取初始化的服务实例清单;另一个getUpdatedListOfServers用于获取更新的服务实例清单。
2. ServerList接口有很多实现类,DynamicServerListLoadBalancer默认使用了DomainExtractingServerList类作为ServerList的实现,但是在DomainExtractingServerList的构造方法中又传入了DiscoveryEnabledNIWSServerList对象,查看源码发现最终两个清单的获取方式是由DiscoveryEnabledNIWSServerList类来提供的。
3. DomainExtractingServerList类中的obtainServersViaDiscovery方法是用来发现服务实例并获取的,obtainServersViaDiscovery方法的主要逻辑是这样:首先依靠EurekaClient从服务注册中心获取到具体的服务实例InstanceInfo列表,然后对这个列表进行遍历,将状态为UP的实例转换成DiscoveryEnabledServer对象并放到一个集合中,最后将这个集合返回。
4. DynamicServerListLoadBalancer中还定义了一个ServerListUpdater.UpdateAction类型的服务更新器,Spring Cloud提供了两种服务更新策略:一种是PollingServerListUpdater,表示定时更新;另一种是EurekaNotificationServerListUpdater表示由Eureka的事件监听来驱动服务列表的更新操作,默认的实现策略是第一种,即定时更新,定时的方式很简单,创建Runnable,调用DynamicServerListLoadBalancer中updateAction对象的doUpdate方法,Runnable延迟启动时间为1秒,重复周期为30秒。
5. 在更新服务清单的时候,调用了我们在第一点提到的getUpdatedListOfServers方法,拿到实例清单之后,又调用了一个过滤器中的方法进行过滤。过滤器的类型有好几种,默认是DefaultNIWSServerListFilter,这是一个继承自ZoneAffinityServerListFilter的过滤器,具有区域感知功能。即它会对服务提供者所处的Zone和服务消费者所处的Zone进行比较,过滤掉哪些不是同一个区域的实例。

综上,DynamicServerListLoadBalancer主要是实现了服务实例清单在运行期间的动态更新能力,同时提供了对服务实例清单的过滤功能。

ZoneAwareLoadBalancer

ZoneAwareLoadBalancer是DynamicServerListLoadBalancer的子类,ZoneAwareLoadBalancer的出现主要是为了弥补DynamicServerListLoadBalancer的不足。由于DynamicServerListLoadBalancer中并没有重写chooseServer方法,所以DynamicServerListLoadBalancer中负责均衡的策略依然是我们在BaseLoadBalancer中分析出来的线性轮询策略,这种策略不具备区域感知功能,这样当需要跨区域调用时,可能会产生高延迟。ZoneAwareLoadBalancer重写了setServerListForZones方法,该方法在其父类中的功能主要是根据区域Zone分组的实例列表,为负载均衡器中的LoadBalancerStats对象创建ZoneStats并存入集合中,ZoneStats是一个用来存储每个Zone的状态和统计信息。重写之后的setServerListForZones方法主要做了两件事:一件是调用getLoadBalancer方法来创建负载均衡器,同时创建服务选择策略;另一件是对Zone区域中的实例清单进行检查,如果对应的Zone下已经没有实例了,则将Zone区域的实例列表清空,防止节点选择时出现异常。

2.我们来看调用堆栈分析

首先回到第9步,自动配置生成

package org.springframework.cloud.netflix.ribbon;


/**
 * @author Dave Syer
 * @author Tim Ysewyn
 */
@SuppressWarnings("deprecation")
@Configuration
@EnableConfigurationProperties
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {	
@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}

从这里可以看到,会依赖自动注入ServerList,IRule等。

package org.springframework.cloud.alibaba.nacos.ribbon;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ServerList;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * integrated Ribbon by default
 * @author xiaojing
 */
@Configuration
@ConditionalOnRibbonNacos
public class NacosRibbonClientConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public ServerList<?> ribbonServerList(IClientConfig config) {
		NacosServerList serverList = new NacosServerList();
		serverList.initWithNiwsConfig(config);
		return serverList;
	}
}

因为我们使用的是NACOS,所以会注入NacosServerList.

IRule使用的是RoundRobinRule.

最后生成ZoneAwareLoadBalancer

3.接着进入到ZoneAwareLoadBalancer的构造,这里主要看DynamicServerListLoadBalancer

 

重点在restOfInit方法。

package com.netflix.loadbalancer;

/**
 * A LoadBalancer that has the capabilities to obtain the candidate list of
 * servers using a dynamic source. i.e. The list of servers can potentially be
 * changed at Runtime. It also contains facilities wherein the list of servers
 * can be passed through a Filter criteria to filter out servers that do not
 * meet the desired criteria.
 * 
 * @author stonse
 * 
 */
public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicServerListLoadBalancer.class);

    boolean isSecure = false;
    boolean useTunnel = false;

    // to keep track of modification of server lists
    protected AtomicBoolean serverListUpdateInProgress = new AtomicBoolean(false);

    volatile ServerList<T> serverListImpl;

    volatile ServerListFilter<T> filter;

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

    protected volatile ServerListUpdater serverListUpdater;



    @Deprecated
    public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, 
            ServerList<T> serverList, ServerListFilter<T> filter) {
        this(
                clientConfig,
                rule,
                ping,
                serverList,
                filter,
                new PollingServerListUpdater()
        );
    }

    public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                                         ServerList<T> serverList, ServerListFilter<T> filter,
                                         ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping);
        this.serverListImpl = serverList;
        this.filter = filter;
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
        }
        restOfInit(clientConfig);
    }


    

    void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
        this.setEnablePrimingConnections(false);
        enableAndInitLearnNewServersFeature();

        updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }

 

 这个ServerListUpdater就是一个定时任务更新服务列表。

 具体的动作为updateAction,即为DynamicServerListLoadBalancer的updateAction

protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
    @Override
    public void doUpdate() {
        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);
}

最后也是updateAllServerList去获取服务列表。

 serverListImpl.getUpdatedListOfServers();就是从NacosServerList服务注册中心获取实例列表。

 获取完后setServersList去设置服务列表。

 

至此,服务列表就获取到allServer,upServer了。

4.然后使用IRULE策略来选择服务,IRULE就会从绑定的LOADBALANCER取出所有的服务列表,然后去选择一个。

 

 

到这里选择完毕。 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值