在使用spring cloud搭建微服务架构时,需要进行负载均衡操作。负载均衡分为硬件负载均衡和软件负载均衡,软件负载均衡又分为服务端负载均衡和客户端负载均衡。本系列主要介绍利用Spring cloud Ribbon 和RestTemplate实现客户端负载均衡,本文主要介绍将逻辑名为host的URI转化为服务实例的过程。
一、客户端负载均衡接口LoadBalanceClient
在进行开发时,要实现对基于RestTemplate的客户端负载均衡,只需在创建RestTemplate对象时,添加LoadBalanced注解即可实现。通过查看源码,该注解是通过LoadBalanceClient接口实现其功能。LoadBalanceClient接口定义三个方法,源码如下:
1 public interface LoadBalancerClient { 2 ServiceInstance choose(String var1); 3 4 <T> T execute(String var1, LoadBalancerRequest<T> var2) throws IOException; 5 6 URI reconstructURI(ServiceInstance var1, URI var2); 7 }
- choose()方法用来从负载均衡器中挑选一个服务实例。
- execute()方法使用选出的服务实例执行具体请求。
- reconstructURI()方法将使用逻辑host名的URI转换为实际的服务实例URI。
这三个方法之间的关系:在自动配置完成相关配置后(下文介绍), execute()方法执行的第一步需要选择一个服务实例,及调用choose()方法(实质上并非使用这个chosse()方法,下文介绍)。选择完服务实例后,调用reconstructURi()方法重新组织成最终的URI,完成URI转换工作。
二、自动配置类LoadBalanceAutoConfiguration
前面从总体上概要介绍了LoadBalanceClient的工作原理,其中提到的自动配置主要用LoadBalancedAutoConfiguration自动配置类完成,源码如下:
@Configuration @ConditionalOnClass({RestTemplate.class}) @ConditionalOnBean({LoadBalancerClient.class}) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired( required = false ) private List<RestTemplate> restTemplates = Collections.emptyList(); public LoadBalancerAutoConfiguration() { } @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List<RestTemplateCustomizer> customizers) { return new SmartInitializingSingleton() { public void afterSingletonsInstantiated() { Iterator var1 = LoadBalancerAutoConfiguration.this.restTemplates.iterator(); while(var1.hasNext()) { RestTemplate restTemplate = (RestTemplate)var1.next(); Iterator var3 = customizers.iterator(); while(var3.hasNext()) { RestTemplateCustomizer customizer = (RestTemplateCustomizer)var3.next(); customizer.customize(restTemplate); } } } }; } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) { return new RestTemplateCustomizer() { public void customize(RestTemplate restTemplate) { List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } @Bean public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient) { return new LoadBalancerInterceptor(loadBalancerClient); } }
负载均衡自动配置类主要提供三个功能,辅助实在客户端负载均衡:
- 创建一个拦截器。当被LoadBalanced注解修饰的RestTemplate发送http请求时,将其拦截并从请求体中获得服务名。
- 创建一个定制器,给RestTemplate添加拦截器。
- 维护一个被LoadBalanced注解修饰的RestTEmplate列表,并对其进行初始化,调用定制器为其添加拦截器。
三、实现客户端负载均衡
第一小节定义了一个实现客户端负载的接口,在Ribbon中其实现类是RibbonLoadBalanceClient,部分源码如下:
public class RibbonLoadBalancerClient implements LoadBalancerClient { private SpringClientFactory clientFactory; public RibbonLoadBalancerClient(SpringClientFactory clientFactory) { this.clientFactory = clientFactory; } 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); Server server = new Server(instance.getHost(), instance.getPort()); boolean secure = this.isSecure(server, serviceId); URI uri = original; if (secure) { uri = UriComponentsBuilder.fromUri(original).scheme("https").build().toUri(); } return context.reconstructURIWithServer(server, uri); } public ServiceInstance choose(String serviceId) { Server server = this.getServer(serviceId); return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); } public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); Server server = this.getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else { RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId); RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server); try { T returnVal = request.apply(ribbonServer); statsRecorder.recordStats(returnVal); return returnVal; } catch (IOException var9) { statsRecorder.recordStats(var9); throw var9; } catch (Exception var10) { statsRecorder.recordStats(var10); ReflectionUtils.rethrowRuntimeException(var10); return null; } } } }
...
protected Server getServer(ILoadBalancer loadBalancer) {
return loadBalancer == null ? null : loadBalancer.chooseServer("default");
}
}
在该类中,通过执行execute()方法下完成URL转换,主要步骤如下:
- 首先要利用choose()方法选择客户端服务实例,其实质时利用netfilex Ribbon定义的ILoadBalancer接口的实现类完成。ILoadBalancer接口定义了实现客户端负载均衡的一些操作,包括添加服务、选择服务、标识服务、服务列表等方法,其基础现类为BaseLoadBalanceer,在基础实现类之上,有DynamicServerListLoadBalancer(实现了服务实例清单在运行期的动态更新能力)、ZoneAwareLoadBalancer(对
DynamicServerListLoadBalancer
的扩展,规避跨区域(Zone)访问,减少延迟)。Spring Clond Ribbon采用的就是ZoneAwareLoadBalancer(区域感知负载均衡器)作为IloadBalancer的实现类,以此对选择客户端服务实例。下一篇文章介绍多种负载均衡器。 - 在选择完服务实例后,chooseServer()方法会返回一个server对象,Ribbon将其包装成一个RibbonServer对象,该对象是ServiceInstance接口的实现类,该实现类包含了服务治理系统中每个服务实例需要提供的一些信息,包括serviceId、host、port等。
- 将获得RibbonServer对象传递给apply()方法,该方法则会利用负载均衡器中的recontructURL()方法重新组织URL,指向实际的服务实例,进行访问。