《Spring Cloud》学习(二) 负载均衡!

时间:2023-03-09 17:22:56
《Spring Cloud》学习(二) 负载均衡!

第二章 负载均衡

  负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。Spring Cloud Ribbon是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于Netflix Ribbon实现,通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模板请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon几乎存在于每一个Spring Cloud 构建的微服务和基础设施中。因为微服务间的调用,API 网关的请求转发等内容实际上都是通过Ribbon来实现的

  通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:

  -服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
  -服务消费者直接通过调用被@LoadBalanced 注解修饰过的 RestTemplate 来实现面向服务的接口调用。

  这样,我们就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。但是RestTemplate是Spring提供。跟Ribbon的客户端负载均衡又有什么关系,又是如何实现?下文详解。

一.LoadBalancerClent 和 RibbonLoadBalancerClient

  1.LoadBalancerClent 

  通过@LoadBalanced注解源码的注释中可以知道,该注解用来给RestTemplate做标记,以使用负载均衡的客户端(LoadBalancerClent)来配置它。下图贴出了LoadBalancerClent源码。

  《Spring Cloud》学习(二) 负载均衡!

  1.1 Servicelnstance choose (String serviceid)根据传入的服务名serviceld, 从负载均衡器中挑选一个对应服务的实例。
  1.2 T execute (String serviceid, LoadBalancerRequest request)使用从负载均衡器中挑选出的服务实例来执行请求内容。
  1.3 URI reconstructURI(Serviceinstance instance , URI original )为系统构建 一个合适的host:port形式的URI。

  从方法名的诠释相信大家已经看出一些倪端,但是并不是大家想的那样(先调用choose挑选服务,再组成URI,最后调用execute执行请求)。因为本人这里这里踩过坑,先强调下!

  重要的是execute方法。

  2.RibbonLoadBalancerClient

  RibbonLoadBalancerClient是LoadBalancerClent的实现类。要点是它实现了父类的execute方法。贴出execute的方法实现。

  《Spring Cloud》学习(二) 负载均衡!

  可以看出进入方法就调用自身的getServer方法获取服务,继续。

  《Spring Cloud》学习(二) 负载均衡!

  IloadBalancer.chooseServer方法解释:通过某种策略,从负载均衡器中挑选出个具体的服务实例也就是这里去真正的去挑选我们调用的服务实例。但是IloadBalancer是一个接口,默认注入的是它的子类实现ZoneAwareLoadBalancer。也就是挑选服务的任务交给了ZoneAwareLoadBalancer。

  至于如何如挑选,在第四节负载均衡策略会详解。

  

二.LoadBalancerAutoConfiguration负载均衡地自动化配置类

  LoadBalancerAutoConfiguration为实现客户端负载均衡地自动化配置类。由于类源码稍长,就不贴了,我下图写出该类主要描述。

  《Spring Cloud》学习(二) 负载均衡!

  1.被@ConditionalOnClass(RestTemplate.class)和@ConditionalOnBean(LoadBalancerClent.class)修饰表示在当前工程中必须有RestTemplate的Bean和LoadBalancerCliet的实现 Bean,理所当然嘛。

  2.创建了一个LoadBalancerInerceptor的Bean,用于实现对客户端发起请求时进行拦截, 以实现客户端负载均衡。这里贴出了LoadBalancerInerceptor的源码,我们可以看到在拦截器中注入了 LoadBalancerClient的实现,其实就是上文提到的RibbonLoadBalancerClient类!

   《Spring Cloud》学习(二) 负载均衡!

  3.创建了一个RestTemplateCustomizer的Bean, 用于给RestTemplate增加LoadBalancerInterceptor拦截器。
  4.维护了一个被@LoadBalanced 注解修饰的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerinInerceptor拦截器。

  我们暂且可以推出:当一个被@LoadBalanced注解修饰的RestTemplate对象向外发起HTTP请求时,会LoadBalancerinterceptor类的intercept函数所拦截,然后调用RibbonLoadBalancerClient类的execute函数。继续向下看!

三.ZoneAwareLoadBalancer负载均衡器

  

  我们已经知道ZoneAwareLoadBalancer是ILoadBalancer的实现类,并且ZoneAwareLoadBalancer.chooseServer方法是挑选出个具体的服务实例。但是ILoadBalancer和ZoneAwareLoadBalancer并不是直接实现关系,他们有多层实现继承关系,如下图。

  《Spring Cloud》学习(二) 负载均衡!

  他们对ILoadBalancer一层层的实现扩展构成了ZoneAwareLoadBalancer,这里对他们具体如何实现扩展不做描述,大家有兴趣的可以去了解下!

  ZoneAwareLoadBalancer中要点:

  1.定义了负载均衡的处理规则IRule对象,负载均衡器实际将服务实例选择任务又委托给了IRule 实例中的choose函数来实现。而在这里,默认初始化了 RoundRobinRule为IRule的实现对象。RoundRobinRule 实现了最基本且常用的线性负载均衡规则(轮询),换而言之就是IRule的实现类就是定义不同的服务选择规则,比如轮询,随机,权重等等

  2.实现了服务实例清单在运行期的动态更新能力。同时它还具备了对服务实例清单的过滤功能:我们可以通过过滤器来选择性地获取一批服务实例清单。它定义了两个抽象方法:getInitialListOfServers用于获取初始化的服务实例清单, 而getUpdatedListOfServers用于获取更新的服务实例清单。

  在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心,如上一章我们介绍的Eureka服务端。在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。

  当Ribbon与Eureka联合使用时,Ribbon的服务实例清单RibbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务端列表。目的是为了对服务清单进行负载均衡需要的封装处理。利用Eureka的事件监听器来驱动服务列表的更新操作。它先创建了一个Runnable的线程实现,在该实现中调用了更新服务实例列表的方法, 最后再为这个Runnable线程实现启动了一个定时任务(默认30秒)来执行。

  简而言之ZoneAwareLoadBalancer的职责就是获取到服务清单,让IRole以某种规则选取!

  到这里我们就可以先整理下一个简单的负载均衡的流程:

  负载均衡流程:当一个被@LoadBalanced注解修饰的RestTemplate对象向外发起HTTP请求时,会被LoadBalancerinterceptor类的intercept函数所拦截。由于我们在使用RestTemplate时采用了服务名作为host,所以直接从 HttpRequest的URI对象中通过 getHost()就可以拿到服务名,然后调用RibbonLoadBalancerClient类的execute函数,函数中ZoneAwareLoadBalancer的 chooseServer函数通过某种规则获取了负载均衡策略分配到的服务实例对象Server(实质此任务委托给IRule的实现类RoundRobinRule<默认轮询>实例中的choose函数来实现),再将获取服务将其内容包装成RibbonServer对象,然后使用该对象再回调 LoadBalancerinterceptor请求拦截器中LoadBalancerRequest的apply(final ServiceInstance instance) 函数,向一个实际的具体服务实例发起请求,因为服务实例有ip,端口等元数据,从而实现一开始以服务名为host的URI请求到 host:post形式的实际访问地址的转换。

四.IRule负载均衡策略

  我们已经知道了IRule的实现类即是负载均衡规则,通过注入不同实现类来设定不同的选取策略。我们来看看它的一些常用实现类。

  1. RandomRule:该策略实现了从服务实例清单中随机选择一个服务实例的功能。具体是通过rand.nextint(serverCount)函数来获取一个最大为服务数量的随机Int变量,在服务列表通过索引值来返回具体实例。

  2. RoundRobinRule:该策略实现了按照线性轮询的方式依次选择每个服务实例的功能。循环条件内,为服务增加了一个count计数变量,该变量会在每次循环之后累加(线性轮询的实现则是通过Atomicinteger nextServerCyclicCounter对象实现,每次进行实例选择时通过调用 incrementAndGetModulo函数实现递增)。

  3. RetryRule:该策略实现了一个具备重试机制的实例选择功能。默认使用了 RoundRobinRule实例。而在choose方法中则实现了对内部定义的策略进行反复尝试的策略,若期间能够选择到具体的服务实例就返回,若一段时间(可设置)选择不到就根据设置的尝试结束返回 null。

  4. WeightedResponseTimeRule:该策略是对RoundRobinRule 的扩展,增加了根据实例的运行情况来计算权重,并根据权重来挑选实例,以达到更优的分配效果。会创建定时任务(默认30秒)用来为每个服务实例计算权重。权重机制其实是为每个服务生成权重区间,比如根据响应时间为三个服务生成了权重区间:实例A: [0, 100]   实例B: ( 100,150]   实例C: (150,300) (权重计算这里不做描述)。然后会生成一个[0, 最大权重值)区间内的随机数。查看随机数在那个区间,就拿到某个服务实例

  5. BestAvailableRule:该策略在实现中它注入了负载同时在具体的 choose 算法中利用保存的实例统计信息来选择满足要求的实例。通过遍历负载均衡器中维护的所有服务实例,会过滤掉故障的实例,并找出并发请求数最小的一个,所以该策略的特性是可选出最空闲的实例。

  6. PredicateBasedRule:这是一个基于 Predicate 实现的策略, Predicate是对集合进行过滤的条件接口。在choose函数中,先通过子类中实现的 Predicate 逻辑来过滤一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选出一个。

  之前已经知道负载均衡流程,本节也介绍了常用的负载均衡策略,Spring Cloud负载均衡已经有了一定了解。本文也省略了一些内容比如过滤器工作机制,组装host:port的URI,如何封装负载均衡所需的服务清单等等,有机会的话我会单独介绍下!

  


 

本文链接:《Spring Cloud》学习(二) 负载均衡!
转载声明:本博客由静影残月创作。可*转载、引用,但需署名作者且注明文章出处。