ribbon是一個負載均衡組件,它可以將請求分散到多個服務提供者實例中,提高系統的性能和可用性。本章分析ribbon是如何實現負載均衡的
1、@LoadBalanced
消費者在引入ribbon組件后,給http客戶端添加@LoadBalanced注解就能啟用負載均衡功能。@LoadBalanced注解比較簡單,本身沒有包含什么業務邏輯,值得一提的是@Qualifier注解。我們知道@Qualifier通常和@Autowired一起使用,用來指明注入bean的名稱,這樣就能在眾多同類型的bean中選擇真正需要的bean,除此之外@Qualifier也可以不指定名稱,只需要在bean的入口和出口都用@Qualifier修飾,就能建立起對應關系
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
@Component
@Qualifier
public class A {}public class B {// 精準注入A@Autowired@Qualifierprivate A a;
}
2、LoadBalancerAutoConfiguration
和負載均衡真正相關內容在LoadBalancerAutoConfiguration內,查看spring-cloud-common包的spring.factories文件,我們知道這是一個自動配置類
# AutoConfiguration
...
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
...
LoadBalancerAutoConfiguration注入了所有使用@LoadBalanced修飾的restTemplate,為什么加了@LoadBalanced就能引入其他其他被@LoadBalanced修飾的bean,原因就是1中提到的@Qualifier。
LoadBalancerAutoConfiguration內部還有一個loadBalancedRestTemplateInitializerDeprecated方法,這個方法會對restTemplate進行了定制化處理
@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攔截器
@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接口實現類完成的
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方法
// 選擇合適的服務實例
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方法,按照指定的策略選取服務,如輪詢、加權等
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方法獲取服務列表
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方法
調用鏈:
-> DynamicServerListLoadBalancer.updateListOfServers
-> this.serverListImpl.getUpdatedListOfServers;
-> DiscoveryEnabledNIWSServerList.obtainServersViaDiscovery
-> eurekaClient.getInstancesByVipAddress
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中添加攔截器,實現負載均衡功能增強