关于负载均衡

负载均衡一般分为服务器端负载均衡客户端负载均衡
所谓服务器端负载均衡,比如Nginx、F5这些,请求到达服务器之后由这些负载均衡器根据一定的算法将请求路由到目标服务器处理。
所谓客户端负载均衡,比如我们要说的Ribbon,服务消费者客户端会有一个服务器地址列表,调用方在请求前通过一定的负载均衡算法选择一个服务器进行访问,负载均衡算法的执行是在请求客户端进行。
Ribbon是Netflix发布的负载均衡器。Eureka一般配合Ribbon进行使用,Ribbon利用从Eureka中读取到服务信息,在调用服务提供者提供的服务时,会根据一定的算法进行负载。
图片.png

Ribbon高级应用

不需要引入额外的jar坐标,因为在服务消费者中我们引入过eureka-client,它会引入Ribbon相关jar。
图片.png
代码中使用如下,在RestTemplate上添加对应注解即可。

@Bean
// Ribbon负载均衡
@LoadBalanced
public RestTemplate getRestTemplate() {
  return new RestTemplate();
}

Ribbon负载均衡策略

Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶级接口为 com.netflix.loadbalancer.IRule,类树如下
图片.png

负载均衡策略描述
RoundRobinRule:轮询策略默认超过10次获取到的server都不可用,会返回一个空的server
RandomRule:随机策略如果随机到的server为null或者不可用的话,会while不停的循环选取
RetryRule:重试策略一定时限内循环重试。默认继承RoundRobinRule,也支持自定义注入,RetryRule会在每次选取之后,对选举的server进行判断,是否为null,是否alive,并且在500ms内会不停的选取判断。而RoundRobinRule失效的策略是超过10次,RandomRule是没有失效时间的概念,只要serverList没都挂。
BestAvailableRule:最小连接数策略遍历serverList,选取出可用的且连接数最小的一个server。该算法里面有一个LoadBalancerStats的成员变量,会存储所有server的运行状况和连接数。如果选取到的server为null,那么会调用RoundRobinRule重新选取。
AvailabilityFilteringRule:可用过滤策略扩展了轮询策略,会先通过默认的轮询选取一个server,再去判断该server是否超时可用,当前连接数是否超限,都成功再返回。
ZoneAvoidanceRule:区域权衡策略(默认策略扩展了轮询策略,继承了2个过滤器:ZoneAvoidancePredicate和AvailabilityPredicate,除了过滤超时和链接数过多的server,还会过滤掉不符合要求的zone区域里面的所有节点,AWS --ZONE 在一个区域/机房内的服务实例中轮询

注:Netflix公司当时服务部署在亚马逊云AWS上,AWS云服务器在世界各地有多个区域zone,所以Netflix把ZoneAvoidanceRule作为默认策略。

修改负载均衡策略:

#针对被调用方的微服务名称,不加就是全局生效
service-customer:
  ribbon:
    # 负载策略调整
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule 

详细的Ribbon配置放在Feign中写,因为Ribbon不是特别兼容RestTemplate。

Ribbon核心源码剖析

Ribbon工作原理

图片.png
重点:Ribbon给restTemplate添加了一个拦截器
当我们访问http://cloud-eureka-server/shop/order/的时候,ribbon应该根据服务名service-consumer获取到该服务的实例列表并按照一定的负载均衡策略从实例列表中获取一个实例Server,并最终通过RestTemplate进行请求访问。
Ribbon细节结构图(涉及到底层的一些组件/类的描述)

  1. 获取服务实例列表
  2. 从列表中选择一个server
    图片.png
    图中核心是负载均衡管理器LoadBalancer(总的协调者,相当于大脑,为了做事情,协调四肢),围绕它周围的多有IRule、IPing等。
  3. IRule:是在选择实例的时候的负载均衡策略对象
  4. IPing:是用来向服务发起心跳检测的,通过心跳检测来判断该服务是否可用
  5. ServerListFilter:根据一些规则过滤传入的服务实例列表
  6. ServerListUpdater:定义了一系列的对服务列表的更新操作

    @LoadBalanced源码剖析

    为什么在RestTemplate实例上添加了一个@LoadBalanced注解,就可以实现负载均衡?接下来分析这个注解背后的操作(负载均衡过程)

  7. 查看@LoadBalanced注解,这个注解是在哪里被识别到的?
    图片.png
  8. LoadBalancerClient类(实现类RibbonLoadBalancerClient,待用

    public interface LoadBalancerClient extends ServiceInstanceChooser {
      // 根据服务执行请求内容
      <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
      // 根据服务执行请求内容
      <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
      // 拼接请求方式 传统中是ip:port 现在是服务名称:port 形式
      URI reconstructURI(ServiceInstance instance, URI original);
    }
  9. SpringCloud充分利用了SpringBoot的自动装配特点,找spring.factories配置文件
    图片.png
    图片.png
  10. 研究LoadBalancerAutoConfiguration
    图片.png
    LoadBalancerAutoConfiguration里面的内容剖析:
    第一处:注入resttemplate对象到集合待用
    图片.png
    第二处:注入resttemplate定制器
    图片.png
    第三处:使用定制器给集合中的每一个resttemplate对象添加一个拦截器
    图片.png
    至此我们明白,添加了注解的RestTemplate对象会被添加一个拦截器LoadBalancerInterceptor,该拦截器就是后续拦截请求进行负载处理的。
    所以,下一步重点该分析拦截器LoadBalancerInterceptor的intercept()方法
    图片
    RibbonLoadBalancerClient对象是在哪里注入的?回到最初的自动配置类
    RibbonAutoConfiguration中
    图片.png
    负载均衡的事情执行原来交给了我们最初看到的RibbonLoadBalancerClient对象。
    非常核心的一个方法:RibbonLoadBalancerClient.execute()
    图片.png
    关注一:
    图片.png
    回到主配置类RibbonAutoConfiguration
    图片.png
    图片.png
    RibbonClientConfiguration中装配了大脑和肢干
    图片.png
    图片.png
    关注二:
    图片.png
    ZoneAwareLoadBalancer#chooseServer
    因为我们始终在世界的一个区域(中国),所以zone的值为1
    图片.png
    父类:com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
    图片.png
    来到区域隔离策略的父类choose方法中com.netflix.loadbalancer.PredicateBasedRule#choose
    图片.png
    图片.png
    图片.png
    关注三:
    通过打断点和浏览器访问探索
    图片.png
    图片.png
    图片.png
    AbstractClientHttpRequest#execute
    此处,就已经到了RestTemplate底层执行的代码了,由此也将验证最终请求的调用还是靠的RestTemplate
    图片.png
    在进行负载chooseServer的时候,LoadBalancer负载均衡器中已经有了serverList,那么这个serverList是什么时候被注入到LoadBalancer中的?
    来到RibbonClientConfiguration
    图片.png
    图片.png
    把目光聚焦到使用这个空对象ServerList的地方
    图片.png
    图片.png
    图片.png
    图片.png
    进入enableAndInitLearnNewServersFeature()方法
    图片.png
    图片.png
    图片.png

    RoundRobinRule轮询策略源码剖析

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    package com.netflix.loadbalancer;
    
    import com.netflix.client.config.IClientConfig;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class RoundRobinRule extends AbstractLoadBalancerRule {
      private AtomicInteger nextServerCyclicCounter;
      private static final boolean AVAILABLE_ONLY_SERVERS = true;
      private static final boolean ALL_SERVERS = false;
      private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
    
      public RoundRobinRule() {
     this.nextServerCyclicCounter = new AtomicInteger(0);
      }
    
      public RoundRobinRule(ILoadBalancer lb) {
     this();
     this.setLoadBalancer(lb);
      }
    
      // 负载均衡策略类核心方法
      public Server choose(ILoadBalancer lb, Object key) {
     if (lb == null) {
       log.warn("no load balancer");
       return null;
     } else {
       Server server = null;
       int count = 0;
    
       while(true) {
         if (server == null && count++ < 10) {
           // 所有可用服务实例列表
           List<Server> reachableServers = lb.getReachableServers();
           // 所有服务实例列表
           List<Server> allServers = lb.getAllServers();
           int upCount = reachableServers.size();
           int serverCount = allServers.size();
           if (upCount != 0 && serverCount != 0) {
             // 获得一个轮询索引
             int nextServerIndex = this.incrementAndGetModulo(serverCount);
             // 根据索引取出服务实例对象
             server = (Server)allServers.get(nextServerIndex);
             if (server == null) {
               Thread.yield();
             } else {
               // 判断服务可用后返回
               if (server.isAlive() && server.isReadyToServe()) {
                 return server;
               }
    
               server = null;
             }
             continue;
           }
    
         log.warn("No up servers available from load balancer: " + lb);
         return null;
       }
    
       if (count >= 10) {
         log.warn("No available alive servers after 10 tries from load balancer: " + lb);
       }
    
       return server;
     }
      }
    
      private int incrementAndGetModulo(int modulo) {
     int current;
     int next;
     do {
       // 取出上次的计数
       current = this.nextServerCyclicCounter.get();
       // 因为是轮询,计数+1之后对总数取模
       next = (current + 1) % modulo;
     } while(!this.nextServerCyclicCounter.compareAndSet(current, next));
     return next;
      }
    
      public Server choose(Object key) {
     return this.choose(this.getLoadBalancer(), key);
      }
    
      public void initWithNiwsConfig(IClientConfig clientConfig) {
      }
    }

    RandomRule随机策略源码剖析

    图片.png
    图片.png

标签: Ribbon, Spring Cloud

评论已关闭