ribbon是一个负载均衡组件,它可以将请求分散到多个服务提供者实例中,提高系统的性能和可用性。本章分析ribbon是如何实现负载均衡的
1、@LoadBalanced
消费者在引入ribbon组件后,给http客户端添加@LoadBalanced注解就能启用负载均衡功能。@LoadBalanced注解比较简单,本身没有包含什么业务逻辑,值得一提的是@Qualifier注解。我们知道@Qualifier通常和@Autowired一起使用,用来指明注入bean的名称,这样就能在众多同类型的bean中选择真正需要的bean,除此之外@Qualifier也可以不指定名称,只需要在bean的入口和出口都用@Qualifier修饰,就能建立起对应关系
java
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
java
@Component
@Qualifier
public class A {}
public class B {
// 精准注入A
@Autowired
@Qualifier
private A a;
}
2、LoadBalancerAutoConfiguration
和负载均衡真正相关内容在LoadBalancerAutoConfiguration内,查看spring-cloud-common包的spring.factories文件,我们知道这是一个自动配置类
java
# AutoConfiguration
...
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
...
LoadBalancerAutoConfiguration注入了所有使用@LoadBalanced修饰的restTemplate,为什么加了@LoadBalanced就能引入其他其他被@LoadBalanced修饰的bean,原因就是1中提到的@Qualifier。
LoadBalancerAutoConfiguration内部还有一个loadBalancedRestTemplateInitializerDeprecated方法,这个方法会对restTemplate进行了定制化处理
java
@LoadBalanced
@Autowired(
required = false
)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> {
restTemplateCustomizers.ifAvailable((customizers) -> {
Iterator var2 = this.restTemplates.iterator();
while(var2.hasNext()) {
RestTemplate restTemplate = (RestTemplate)var2.next();
Iterator var4 = customizers.iterator();
while(var4.hasNext()) {
RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
// 定制化处理
customizer.customize(restTemplate);
}
}
});
};
}
RestTemplateCustomizer接口的实现类也在LoadBalancerAutoConfiguration内,对restTemplate的定制化其实就是给它添加了RetryLoadBalancerInterceptor拦截器
java
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return (restTemplate) -> {
List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
// 客户端添加拦截器
restTemplate.setInterceptors(list);
};
}
3、RetryLoadBalancerInterceptor
查看RetryLoadBalancerInterceptor的拦截方法,核心语句是loadBalancer.choose,也就是说服务实例的选择功能其实是由LoadBalancerClient接口实现类完成的
java
private final LoadBalancerClient loadBalancer;
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
...
if (serviceInstance == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. Reattempting service instance selection");
}
// 选择服务实例
serviceInstance = this.loadBalancer.choose(serviceName);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Selected service instance: %s", serviceInstance));
}
}
...
ClientHttpResponse response = (ClientHttpResponse)
this.loadBalancer.execute(serviceName, serviceInstance, this.requestFactory.createRequest(request, body, execution));
...
}
RibbonLoadBalancerClient是LoadBalancerClient接口实现类之一,查看它的choose方法
java
// 选择合适的服务实例
public ServiceInstance choose(String serviceId, Object hint) {
Server server = this.getServer(this.getLoadBalancer(serviceId), hint);
return server == null ? null : new RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
}
// 选择合适的服务器
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}
选择服务器的任务移交给了ILoadBalancer的实现类,如ZoneAwareLoadBalancer。ZoneAwareLoadBalancer会调用父类BaseLoadBalancer的choose方法,按照指定的策略选取服务,如轮询、加权等
java
public Server chooseServer(Object key) {
if (this.counter == null) {
this.counter = this.createCounter();
}
this.counter.increment();
if (this.rule == null) {
return null;
} else {
try {
// 按规则选择服务
return this.rule.choose(key);
} catch (Exception var3) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});
return null;
}
}
}
再聊聊服务的来源,LoadBalancer是从哪里挑选的服务?
ZoneAwareLoadBalancer在创建时会调用父类DynamicServerListLoadBalancer的构造方法,然后通过updateListOfServers方法获取服务列表
java
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) {
...
this.restOfInit(clientConfig);
}
void restOfInit(IClientConfig clientConfig) {
...
this.updateListOfServers();
...
}
@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList();
if (this.serverListImpl != null) {
// 获取服务列表
servers = this.serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
if (this.filter != null) {
servers = this.filter.getFilteredListOfServers((List)servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
}
}
this.updateAllServerList((List)servers);
}
看到服务实例自然联想到eureka,继续跟踪getUpdatedListOfServers方法
java
调用链:
-> DynamicServerListLoadBalancer.updateListOfServers
-> this.serverListImpl.getUpdatedListOfServers;
-> DiscoveryEnabledNIWSServerList.obtainServersViaDiscovery
-> eurekaClient.getInstancesByVipAddress
java
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
...
// eureka客户端拉取服务
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion);
Iterator var8 = listOfInstanceInfo.iterator();
...
}
方法体中出现eureka客户端,也就是说ribbon选择的服务实例其实来自于eurka服务端,是通过eureka客户端拉取到本地的
3、总结
ribbon组件向restTemplate中添加拦截器,实现负载均衡功能增强