SpringCloud系列 (五)Ribbon负载均衡

435 阅读14分钟

Ribbon负载均衡

1. 关于负载均衡

负载均衡⼀般分为服务器端负载均衡客户端负载均衡

所谓服务器端负载均衡,⽐如Nginx、F5这些,请求到达服务器之后由这些负载均衡器根据⼀定的算法将请求路由到⽬标服务器处理。

所谓客户端负载均衡,⽐如我们要说的Ribbon,服务消费者客户端会有⼀个服务器地址列表,调⽤⽅在请求前通过⼀定的负载均衡算法选择⼀个服务器进⾏访问,负载均衡算法的执⾏是在请求客户端进⾏。

Ribbon是Netflix发布的负载均衡器。Eureka⼀般配合Ribbon进⾏使⽤,Ribbon利⽤从Eureka中读取到服务信息,在调⽤服务提供者提供的服务时,会根据⼀定的算法进⾏负载

2. Ribbon⾼级应⽤

下面我们把目光放到Eureka客户端项目 lagou-service-autodeliver,这个项目不需要引⼊额外的Jar坐标,因为在服务消费者中我们引⼊过eureka-client,它会引⼊Ribbon相关Jar(观察Maven的引用依赖得知)

(1)改造简历微服务

接下来,我们需要改造我们的简历微服务的项目,现在就两个简历微服务了分别是8080,8081

运行项目后,可以观察到两个服务都已经注册到了Eureka中,这样,我们就可以开始测试负载均衡相关的业务了

为了便于区分在负载均衡时究竟访问的是哪个服务,我们可以改造两个简历微服务,就让他返回IP端口号

@RestController
@RequestMapping("/resume")
public class ResumeController {
 
    @Value("${server.port}")
    private Integer port;

    //http://localhost:8081/resume/openstate/1545133
    @GetMapping("/openstate/{userId}")
    public Integer findDefaultResumeState(@PathVariable Long userId){
        return port;
    }
}

(2)改造简历投递服务[微服务使用端]

为了让Eureka客户端启用Ribbon,我们只需要在注册RestTemplate时,增加一个@LoadBalanced注解,这样就能使用Ribbon了

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

RestTemplate加入@LoadBalanced后,就需要改造一下RestTemplate的调用方式了

没有引入Ribbon之前的方式 是需要和Eureka Server直接打交道获取相关的服务实例信息,然后选择一个进行调用

  @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/old2/checkState/{userId}")
    public Integer findResumeOpenState_old2(@PathVariable Long userId){

        //1. 从Eureka Server中获取 服务的实例信息
        List<ServiceInstance> instances = discoveryClient.getInstances("lagou-service-resume");

        //2、如果有多个实例,选择一个使用
        ServiceInstance serviceInstance = instances.get(0);

        //3、从元数据信息获取host port
        String host = serviceInstance.getHost();
        int port = serviceInstance.getPort();
        String url="http://"+host+":"+port+"/resume/openstate/" + userId;

        System.out.println("Eureka服务信息>>>>>>>>>>>>>>>"+url);


        Integer forObject = restTemplate.getForObject(url ,Integer.class);

        return forObject;
    }

引入Ribbon之后的方式 客户端只需要知道服务名称即可,IP和端口号不必知晓,这样的一个优势是:服务端的物理Ip可以灵活变动,服务器迁移什么的,对业务不会产生影响,因为客户端只认服务名称,而服务名称又是固定的。最为重要的是,Ribbon封装了Ip端口等细节,直接使用服务名就能完成调用了,Ribbon内部自己封装了相关负载均衡策略,IP组装等业务

@GetMapping("/checkState/{userId}")
public Integer findResumeOpenState(@PathVariable Long userId){

    String url="http://lagou-service-resume/resume/openstate/" + userId;

    Integer forObject = restTemplate.getForObject(url ,Integer.class);

    return forObject;
}

(3)针对简历投递服务,进行调用

调用原本的简历投递服务后,端口号会8080,8081两个端口号进行切换

Ribbon负载均衡示例代码

3. Ribbon负载均衡策略

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

负载均衡策略描述
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重新选取。1(1) 2(1) 3(1)
AvailabilityFilteringRule:可⽤过滤策略扩展了轮询策略,会先通过默认的轮询选取⼀个server,再去判断该server是否超时可⽤,当前连接数是否超限,都成功再返回。
ZoneAvoidanceRule:区域权衡策略(默认策略)扩展了轮询策略,继承了2个过滤器:ZoneAvoidancePredicate和AvailabilityPredicate,除了过滤超时和链接数过多的server,还会过滤掉不符合要求的zone区域⾥⾯的所有节点,AWS --ZONE 在⼀个区域/机房内的服务实例中轮询

如何修改负载均衡策略

打开配置文件application.yml进行修改

#针对的被调⽤⽅微服务名称,不加就是全局⽣效
lagou-service-resume:
  ribbon:
   NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载策略调整

# 全局⽣效
#ribbon:
#  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载策略调整

修改完后执行简历投递服务,发现断点进入了 RandomRule中

4. Ribbon核⼼源码剖析

Ribbon⼯作原理

重点:Ribbon给restTemplate添加了⼀个拦截器

思考:Ribbon在做什么:

当我们访问http://lagou-service-resume/resume/openstate 的时候,ribbon应该根据服务名lagou-service-resume获取到该服务的实例列表并按照⼀定的负载均衡策略从实例列表中获取⼀个实例Server,并最终通过RestTemplate进⾏请求访问

Ribbon细节结构图(涉及到底层的⼀些组件/类的描述)

图中核⼼是负载均衡管理器LoadBalancer(总的协调者,相当于⼤脑,为了做事情,协调四肢),围绕它周围的多有IRule、IPing等

  • IRule:是在选择实例的时候的负载均衡策略对象
  • IPing:是⽤来向服务发起⼼跳检测的,通过⼼跳检测来判断该服务是否可⽤
  • ServerListFilter:根据⼀些规则过滤传⼊的服务实例列表
  • ServerListUpdater:定义了⼀系列的对服务列表的更新操作

4.1 @LoadBalanced源码剖析

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

查看@LoadBalanced注解,那这个注解是在哪⾥被识别到的呢?根据我们的一般阅读源码经验,一般的入口处都会有一些@Import之类的注解,但是这里却没有其它信息,只有一行注释 使用@LoadBalanced注解后可以将普通的 RestTemplate对象使用 LoadBanlancerClient去处理

思路貌似中断了,所以现在只能利用SpringBoot自动装配的原理,去Ribbon源码处去探索一下

进到RibbonAutoConfiguration类中,发现有@AutoConfigureBefore 和 @AutoConfigureAfter 注解

LoadBalancerAutoConfiguration

##================================================================================
##  只有存在RestTemplate类的时候,才会装配生效,所以在Http调用过程当中,我们需要使用RestTemplate
##================================================================================
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    ##================================================================================
    ## (1)声明一个List<RestTemplate>集合对象,此处会自动注入那些添加了@LoadBalanced注解的RestTemplate对象(统一这里集中)
    ## 注⼊resttemplate对象到集合待⽤
    ##================================================================================
    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

 
    ##================================================================================
    ## (2)向容器注入restTemplate定制器
    ## 效果为:给restTemplate对象添加一个拦截器LoadBalancerInterceptor
    ##================================================================================
    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) {
    return restTemplate -> {
        List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        restTemplate.setInterceptors(list);
    };
 
    ##================================================================================
    ## (3)使⽤定制器给集合中的每⼀个resttemplate对象添加⼀个拦截器 
    ##================================================================================
    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
        
        	## 遍历所有的resttemplate对象,给每⼀个resttemplate对象配置⼀个拦截器 
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
    } 
}

这边我们可以看到,拦截器 LoadBalancerInterceptor就是后续的核心,这里先来看下intercept方法

####################################################################################### 
##  org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
#####################################################################################
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

  private LoadBalancerClient loadBalancer;
  private LoadBalancerRequestFactory requestFactory;
  public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
    this.loadBalancer = loadBalancer;
    this.requestFactory = requestFactory;
  }

  @Override
  public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
  final ClientHttpRequestExecution execution) throws IOException {
    ## 获取拦截到的请求uri,比如我们访问的 :http://lagou-servver-resume/resume/xxxx
    final URI originalUri = request.getURI();

    ## 获取uri中的服务名:lagou-service-resumt
    String serviceName = originalUri.getHost();

    ## 剩下的负载均衡的事情,交给LoadBalancerClient对象负责执行
    return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
  }
}

那么?RibbonLoadBalancerClient对象是在哪⾥注⼊的===> 回到最初的⾃动配置类RibbonAutoConfiguration中

####################################################################################### 
##  org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#loadBalancerClient
#####################################################################################
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {

	## 注入了一个LoadBalancerClient对象,实例为 RibbonLoadBalancerClient
	return new RibbonLoadBalancerClient(springClientFactory());
}

我们继续来分析这句比较关键的代码

return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));

然后从execute方法点进去

####################################################################################### 
##  org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute
##################################################################################### 
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
  
  ## 1、获取一个负载均衡器
  ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
  
  ## 2、负载均衡器选择一个最终要使用的server实例对象
  Server server = getServer(loadBalancer, hint);
  
  ## 把server封装成Ribbonserver对象
  RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
  serviceId), serverIntrospector(serviceId).getMetadata(server));
  
  ## 3、继续执行
  return execute(serviceId, ribbonServer, request);
}
1、getLoadBalancer

先来看下 getLoadBalancer的方法,发现调用的是 SpringClientFactory的方法,那么我们自然而然的就要想,这个 Factory又是什么给注入进来的

  ####################################################################################### 
  ##  org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient 
  #######################################################################################
  private SpringClientFactory clientFactory;

  public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
  	this.clientFactory = clientFactory;
  }
    
  protected ILoadBalancer getLoadBalancer(String serviceId) {
  	return this.clientFactory.getLoadBalancer(serviceId);
  }

回到RibbonAutoConfiguration配置主类中,看到是这里注入的SpringClientFactory,我们在追代码的时候,很多时候得反反复复来回翻,只要不晕就行了

  ####################################################################################### 
  ##  org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#springClientFactory
  #######################################################################################
  @Bean
  public SpringClientFactory springClientFactory() {
    SpringClientFactory factory = new SpringClientFactory();
    factory.setConfigurations(this.configurations);
    return factory;
  }

既然已经知道了什么时候注入的,那么再次把目光回到getLoadBalancer方法中,其实到这里,我们已经无法继续进行下去了,因为都已经到头了,但是这里貌似看起来是从容器中获取 ILoadBalancer.class类型的数据

  ####################################################################################### 
  ##  org.springframework.cloud.netflix.ribbon.SpringClientFactory#getLoadBalancer
  #######################################################################################
  ## 基本获取的方法	
  public ILoadBalancer getLoadBalancer(String name) {
    return getInstance(name, ILoadBalancer.class);
  }
  
  ## 继续跟踪 ==>  发现调用 super.getInstance(name, type);
  @Override
  public <C> C getInstance(String name, Class<C> type) {
    C instance = super.getInstance(name, type);
    if (instance != null) {
    	return instance;
    }
    IClientConfig config = getInstance(name, IClientConfig.class);
    return instantiateWithConfig(getContext(name), type, config);
  }
  
  ## 继续跟踪==> 看到了比较熟悉的 context.getBean(type)方法,===> 证明,ILoadBalancer.class 应该早早的就注入到了容器当中了
  public <T> T getInstance(String name, Class<T> type) {
    AnnotationConfigApplicationContext context = getContext(name);
      if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,   type).length > 0) {
      	return context.getBean(type);
      }
    return null;
  }

那答案的关键在哪里?再次看看SpringClientFactory类,发现有个父类构造函数,应该是真理入口处,这里传入了类型RibbonClientConfiguration.class,根据我们的一般经验,这个配置类,应该会在方法内部进行加载

####################################################################################### 
##  org.springframework.cloud.netflix.ribbon.SpringClientFactory
#######################################################################################
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
  public SpringClientFactory() {
  	super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
  }
}

果不其然,这个配置类里面一堆的配置信息

####################################################################################### 
##  org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
#######################################################################################

## 配置了负载均衡策略
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {

  ## 如果默认配置了负载均衡策略,则以配置文件为准
  if (this.propertiesFactory.isSet(IRule.class, name)) {
  	return this.propertiesFactory.get(IRule.class, config, name);
  }
  ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
  rule.initWithNiwsConfig(config);
  return rule;
}

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
  if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
  	return this.propertiesFactory.get(ILoadBalancer.class, config, name);
  }
  
  ## 默认注入的负载均衡器
  return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,serverListFilter, serverListUpdater);
}
2、getServer

接下来看下如何获取一个Server的

####################################################################################### 
##  org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#getServer 
#######################################################################################

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
  if (loadBalancer == null) {
 	 return null;
  }
  // Use 'default' on a null hint, or just pass it on?
  return loadBalancer.chooseServer(hint != null ? hint : "default");
}

追踪到最后,到达了一个接口,可以看到有个三个实现,根据我们上诉的分析,我们知道,我们的默认的实现类是 ZoneAwareLoadBalancer

当我们进入chooseServer方法时,就看到了一个return 语句,这里需要插入一个小的知识点:ribbon之前是在亚马逊云上,所以他们有分区的概念,而在我们国家,这个不怎么使用,所以第一步就进入了return分支

  @Override
  public Server chooseServer(Object key) {
    if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
      logger.debug("Zone aware logic disabled or there is only one zone");
      return super.chooseServer(key);
    }
  }

我们进入其父类 super.chooseServer(key);

####################################################################################### 
##  com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
####################################################################################### 
public Server chooseServer(Object key) {
  if (counter == null) {
  	counter = createCounter();
  }
  counter.increment();
  if (rule == null) {
  	return null;
  } else {
    try {
        ##  根据规则,选择一个负载均衡实例,默认是aws那个
    	return rule.choose(key);
    } catch (Exception e) {
      logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
      return null;
    }
  }
}

然后继续深入,进入到

####################################################################################### 
##  com.netflix.loadbalancer.PredicateBasedRule
####################################################################################### 
  public Server choose(Object key) {
    ILoadBalancer lb = getLoadBalancer();
    
    ## 从过滤之后的服务实例集合中根据轮询策略选择第一个server
    Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
    if (server.isPresent()) {
  	  return server.get();
    } else {
    	return null;
    }       
  }
####################################################################################### 
##  com.netflix.loadbalancer.AbstractServerPredicate#chooseRoundRobinAfterFiltering
#######################################################################################  

public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
  ## 获取有资格的服务实例
  List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
  
  ## 如果服务实例为空,则返回一个默认值
  if (eligible.size() == 0) {
    return Optional.absent();
  }
  
  ## incrementAndGetModulo==> 轮询到实例索引值计算方法
  return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
####################################################################################### 
##  com.netflix.loadbalancer.AbstractServerPredicate#incrementAndGetModulo
#######################################################################################  
private final AtomicInteger nextIndex = new AtomicInteger();
    
private int incrementAndGetModulo(int modulo) {
  for (;;) {
  
    ## 获取当前服务实例的索引值 
    int current = nextIndex.get();
    
    ## 通过取余的方式记录下一个索引值
    int next = (current + 1) % modulo;
    
    ## 通过CAS设置下一个索引值(解决并发场景可能造成的数据问题)
    if (nextIndex.compareAndSet(current, next) && current < modulo)
    	return current;
  }
}
3、execute

我们再来回到第三步,execute

####################################################################################### 
##   org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute
#######################################################################################   
 
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
  ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
  Server server = getServer(loadBalancer, hint);
  
  if (server == null) {
	  throw new IllegalStateException("No instances available for " + serviceId);
  }
  
  RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
  serviceId), serverIntrospector(serviceId).getMetadata(server));
  
  ## ======> execute
  return execute(serviceId, ribbonServer, request);
}

我们进入execute方法后,观察这个方法里面的代码,发现这里有个最为关键的方法 request.apply(serviceInstance);,这个方法内部,有比较多的代码,但是我们的进入这个方法的目的,是为了想知道,当前方法时如何执行远程调用的,所以,我们的目光不是其他的旁枝末节,而是扎到那些像 请求的语句

####################################################################################### 
##   org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute
#######################################################################################  

@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
  Server server = null;
  if(serviceInstance instanceof RibbonServer) {
  server = ((RibbonServer)serviceInstance).getServer();
  }
  if (server == null) {
  throw new IllegalStateException("No instances available for " + serviceId);
  }

  RibbonLoadBalancerContext context = this.clientFactory .getLoadBalancerContext(serviceId);
  
  RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

  ## 向server实例发起请求的关键步骤
  T returnVal = request.apply(serviceInstance);
  statsRecorder.recordStats(returnVal);
  return returnVal;

  return null;
}

这次我们点击到方法apply,发现根本无法追踪了,所以我们只好根据断点调试,进入到 request.apply(serviceInstance)方法中

####################################################################################### 
##   org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory#createRequest
#######################################################################################  

public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
final byte[] body, final ClientHttpRequestExecution execution) {
  return instance -> {
    HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
    if (transformers != null) {
      for (LoadBalancerRequestTransformer transformer : transformers) {
      	serviceRequest = transformer.transformRequest(serviceRequest, instance);
      }
    }
    
    ## ===> 目光聚焦到这里,在源码中,类似do,execute什么的,一般都是比较重要的方法
    return execution.execute(serviceRequest, body);
  };
}

再次跟踪

org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution#execute

@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
  HttpMethod method = request.getMethod();
  Assert.state(method != null, "No standard HTTP method");
  ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
  request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
  if (body.length > 0) {
    if (delegate instanceof StreamingHttpOutputMessage) {
      StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
      streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
    }
    else {
      StreamUtils.copy(body, delegate.getBody());
    }
  }
  
  ## ==> 代理类的execute方法
  return delegate.execute();
}

此处,就已经到了RestTemplate底层执⾏的代码了,由此也将验证最终请求的调⽤还是靠的RestTemplate

####################################################################################### 
##   org.springframework.http.client.AbstractClientHttpRequest#execute
#######################################################################################  
 
@Override
public final ClientHttpResponse execute() throws IOException {
  assertNotExecuted();
  ClientHttpResponse result = executeInternal(this.headers);
  this.executed = true;
  return result;
}

接下来,在进⾏负载chooseServer的时候,LoadBalancer负载均衡器中已经有了serverList,那么这个serverList是什么时候被注⼊到LoadBalancer中的,它的⼀个机制⼤概是怎样的?

来到RibbonClientConfiguration

####################################################################################### 
##   org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonLoadBalancer
#######################################################################################  

## serverList  ==> 在构造函数中,使用serverList,说明在其他的地方,肯定注入了serverList对象
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
        ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
        IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
  if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
  	return this.propertiesFactory.get(ILoadBalancer.class, config, name);
  }
  
  ## 在这里我们可以看到,这里咋构建 一个负载均衡器时,直接就使用了serverList
  return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
  serverListFilter, serverListUpdater);
}

向容器中注入ServerList Bean对象,该对象没有并么有发现有实际做了什么事情,由此,我们猜想,这里我们先注入一个空对象,对象里面的数据赋值,应该在后续的过程当中

@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
  if (this.propertiesFactory.isSet(ServerList.class, name)) {
    return this.propertiesFactory.get(ServerList.class, config, name);
  }
  
  ## new一个基本配置的serverList
  ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
  
  ## 初始化配置信息
  serverList.initWithNiwsConfig(config);
  
  ## 直接返回
  return serverList;
}

那么serverList的后续的赋值一定是在这个类里面

继续往上翻

public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                           ServerList<T> serverList, ServerListFilter<T> filter,
                           ServerListUpdater serverListUpdater) {
  super(clientConfig, rule, ping);
  this.serverListImpl = serverList;
  this.filter = filter;
  this.serverListUpdater = serverListUpdater;
  if (filter instanceof AbstractServerListFilter) {
  	((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
  }
  
  ## ==> 关键调用
  restOfInit(clientConfig);
}

最为关键的方法

void restOfInit(IClientConfig clientConfig) {
  boolean primeConnection = this.isEnablePrimingConnections();
  // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
  this.setEnablePrimingConnections(false);
  
  ## 该方法会开启 一个延时定时任务,若干时间之后每隔一定时间就取Eureka Client缓存中获取新的服务实例信息(EeurakaClient)中也会定时从EurekaServer更新服务信息),然后更新到Ribbon中
  enableAndInitLearnNewServersFeature();

  ##  因为上面定义了延时定时任务,并没有马上执行,这里紧接着就立马执行一次
  updateListOfServers();
  
  if (primeConnection && this.getPrimeConnections() != null) {
  this.getPrimeConnections()
  .primeConnections(getReachableServers());
  }
  this.setEnablePrimingConnections(primeConnection); 
}
enableAndInitLearnNewServersFeature 相关的代码
####################################################################################### 
##   com.netflix.loadbalancer.DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature
#######################################################################################  

public void enableAndInitLearnNewServersFeature() {
  LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
  
  ## serverList更新器,执行 更新执行操作
  serverListUpdater.start(updateAction);
}

## updateAction是一个类对象,该类中有一个doUpdate方法,核心逻辑偶就是updateListOfServers
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
  @Override
  public void doUpdate() {
  	updateListOfServers();
  }
};

这里看一下,服务列表的更新器的 start方法

####################################################################################### 
##   com.netflix.loadbalancer.PollingServerListUpdater#start
#######################################################################################  

@Override
public synchronized void start(final UpdateAction updateAction) {
  if (isActive.compareAndSet(false, true)) {
  
      ###  ====> 定义了线程,逻辑就是调用传进来的updateAction的 doUpdate方法
      final Runnable wrapperRunnable = new Runnable() {
          @Override
          public void run() {
              if (!isActive.get()) {
                  if (scheduledFuture != null) {
                      scheduledFuture.cancel(true);
                  }
                  return;
              }
              try {
                  ##  ==> 执行更新操作
                  updateAction.doUpdate();
                  lastUpdated = System.curr 
          }
      };
      
      
      ###  ===> 定时了延时定时任务,定时任务逻辑就偶是上面的线程逻辑(定时更新服务信息)
      scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
              wrapperRunnable,
              initialDelayMs,
              refreshIntervalMs,
              TimeUnit.MILLISECONDS
      );
  } else {
      logger.info("Already active, no-op");
  }
}

4.2 RoundRobinRule轮询策略源码剖析

public class RoundRobinRule extends AbstractLoadBalancerRule {

## 负载均衡策略类核心类方法
public Server choose(ILoadBalancer lb, Object key) {
  if (lb == null) {
      log.warn("no load balancer");
      return null;
  }

  Server server = null;
  int count = 0;
  
  ## 这里加入了重试策略 ,重试10while (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)) {
          log.warn("No up servers available from load balancer: " + lb);
          return null;
      }
	 
      ## 根据CAS,取模算法,对每个服务器进行轮询
      int nextServerIndex = incrementAndGetModulo(serverCount);
      
      ## 根据索引获取服务
      server = allServers.get(nextServerIndex);

      if (server == null) {
          /* Transient. */
          Thread.yield();
          continue;
      }

      ## 判断服务可用后返回
      if (server.isAlive() && (server.isReadyToServe())) {
          return (server);
      }

      // Next.
      server = null;
  }

  if (count >= 10) {
      log.warn("No available alive servers after 10 tries from load balancer: "
              + lb);
  }
  return server;
}

private int incrementAndGetModulo(int modulo) {
  for (;;) {
  
      ## 取出上次的计数
      int current = nextServerCyclicCounter.get();
      
      ## 因为是取模,计数+1后,进行取模
      int next = (current + 1) % modulo;
      if (nextServerCyclicCounter.compareAndSet(current, next))
          return next;
  }
}

}

4.3 RandomRule随机策略源码剖析

public class RandomRule extends AbstractLoadBalancerRule {

/**
 * Randomly choose from all living servers
 */
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
  public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        return null;
    }
    Server server = null;
	
    ##  当服务为空的时候,会一直的获取,没有10的限制
    while (server == null) {
        if (Thread.interrupted()) {
            return null;
        }
        List<Server> upList = lb.getReachableServers();
        List<Server> allList = lb.getAllServers();

        int serverCount = allList.size();
        if (serverCount == 0) {
            return null;
        }

		## 获取索引的时候,直接返回随机数字
        int index = chooseRandomInt(serverCount);
        server = upList.get(index);

        if (server == null) {
            Thread.yield();
            continue;
        }

        if (server.isAlive()) {
            return (server);
        }

        // Shouldn't actually happen.. but must be transient or a bug.
        server = null;
        Thread.yield();
    }

    return server;

  }

  protected int chooseRandomInt(int serverCount) {
  
    ## 这里使用了线程安全的 ThreadLocalRandom 返回一个随机数字
    return ThreadLocalRandom.current().nextInt(serverCount);
  }

}

Ribbon示例代码