深入理解Spring Cloud Ribbon客户端负载均衡原理(一 实现服务实例地址转换)

在使用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转换,主要步骤如下:

  1. 首先要利用choose()方法选择客户端服务实例,其实质时利用netfilex Ribbon定义的ILoadBalancer接口的实现类完成。ILoadBalancer接口定义了实现客户端负载均衡的一些操作,包括添加服务、选择服务、标识服务、服务列表等方法,其基础现类为BaseLoadBalanceer,在基础实现类之上,有DynamicServerListLoadBalancer(实现了服务实例清单在运行期的动态更新能力)、ZoneAwareLoadBalancer(对DynamicServerListLoadBalancer的扩展,规避跨区域(Zone)访问,减少延迟)。Spring Clond Ribbon采用的就是ZoneAwareLoadBalancer(区域感知负载均衡器)作为IloadBalancer的实现类,以此对选择客户端服务实例。下一篇文章介绍多种负载均衡器。
  2. 在选择完服务实例后,chooseServer()方法会返回一个server对象,Ribbon将其包装成一个RibbonServer对象,该对象是ServiceInstance接口的实现类,该实现类包含了服务治理系统中每个服务实例需要提供的一些信息,包括serviceId、host、port等。
  3. 将获得RibbonServer对象传递给apply()方法,该方法则会利用负载均衡器中的recontructURL()方法重新组织URL,指向实际的服务实例,进行访问。
原文地址:https://www.cnblogs.com/guojuboke/p/10417642.html