本文从源码视角简述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的负载均衡。