• 深入理解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,指向实际的服务实例,进行访问。
  • 相关阅读:
    公司内部图书管理界面原型设计图
    对象的判等
    虚方法的调用
    类的初始化顺序
    A good idea: TotT – Testing on the Toilet
    变量命名
    QSignalMapper
    dxsdk出错,代码写完后按这个solution试下
    SVG 我太土了。。
    gsl在vc下编译时一个问题
  • 原文地址:https://www.cnblogs.com/guojuboke/p/10417642.html
Copyright © 2020-2023  润新知