Springcloud源码阅读4-Ribbon负载均衡(下)

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
简介: Springcloud源码阅读4-Ribbon负载均衡(下)

关联阅读:


SpringCloud源码阅读0-SpringCloud必备知识

SpringCloud源码阅读1-EurekaServer源码的秘密

SpringCloud源码阅读2-Eureka客户端的秘密

SpringCloud源码阅读3-Ribbon负载均衡(上)


配置文件


同其他微服务组件与spring整合过程一样,Ribbon也有一个自动配置文件。 RibbonAutoConfiguration

@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
  //加载配置规范
  @Autowired(required = false)
  private List<RibbonClientSpecification> configurations = new ArrayList<>();
  //加载饥饿属性。
  @Autowired
  private RibbonEagerLoadProperties ribbonEagerLoadProperties;
  // Ribbon特征类
  @Bean
  public HasFeatures ribbonFeature() {
    return HasFeatures.namedFeature("Ribbon", Ribbon.class);
  }
  // 客户端生产工厂
  @Bean
  public SpringClientFactory springClientFactory() {
    SpringClientFactory factory = new SpringClientFactory();
    factory.setConfigurations(this.configurations);
    return factory;
  }
  //负载均衡客户端
  @Bean
  @ConditionalOnMissingBean(LoadBalancerClient.class)
  public LoadBalancerClient loadBalancerClient() {
    return new RibbonLoadBalancerClient(springClientFactory());
  }
  .......
  .......
}

下面讲讲配置文件中所包含的知识点。


RibbonClientSpecification:

RibbonClient规范,一个规范就对应一种类型的RibbonClient。 规范怎么制订呢?

  • @RibbonClients : 针对全部服务指定规范的。
  • @RibbonClient: 针对部分指定规范的。

此两个注解都会引入一个RibbonClientConfigurationRegistrar类。 从其名字,我们也可以看出,这是一个用来注册客户端配置的注册类。

RibbonClientConfigurationRegistrar会把 @RibbonClients  与 @RibbonClient 注解对应的配置类,注册为一个RibbonClientSpecification类的Bean.

  • 对应的配置类作为构造函数的参数,传入。
  • 针对的服务名,作为构造参数传入。

这样就得到了RibbonClientSpecification 规范列表。

private void registerClientConfiguration(BeanDefinitionRegistry registry,
      Object name, Object configuration) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder
        .genericBeanDefinition(RibbonClientSpecification.class);
    builder.addConstructorArgValue(name);//客户端名称
    builder.addConstructorArgValue(configuration);//对应的配置类。
    registry.registerBeanDefinition(name + ".RibbonClientSpecification",
        builder.getBeanDefinition());
  }

Ribbon负载均衡(上)一节说过,@RibbonClients 和 @RibbonClient 用来自定义客户端组件,替换默认的组件。

所以所谓规范的不同,其实就是表现在 个别组件的不同

注意: @RibbonClients 的value属性,可以用来配置@RibbonClient的复数 @RibbonClients(value = {@RibbonClient(name = "xxx",configuration = XxxRibbonConfig.class),@RibbonClient(name = "demo",configuration = DemoRibbonConfig.class) })

@RibbonClients 的defaultConfiguration属性,用来替换所有非自定义的客户端的默认组件


SpringClientFactory:

每个微服务都在调用多个微服务。调用不同微服务的RibbonClient配置可能不同。SpringClientFactory根据不同的RibbonClient规范(RibbonClientSpecification),为不同的微服务创建子上下文。来存储不同规范的RibbonClient  组件Bean。 以此达到个性化并存的目的。

从代码中,可以看出,SpringClientFactory  会传入List configurations

@Bean
  public SpringClientFactory springClientFactory() {
    SpringClientFactory factory = new SpringClientFactory();
    factory.setConfigurations(this.configurations);
    return factory;
  }

举例说明: user服务需要调用, A B C三个微服务,使用@RibbonClient(name = "A", configuration = AConfiguration.class)针对A服务 自定义了IPing 为MyIPing。 那么会创建三个上下文:

  • A的上下文,使用A.RibbonClientSpecification 规范创建, IPing 对应的Bean是 MyMyIPing
  • B的上下文,使用default.RibbonClientSpecification  规范创建,IPing 对应的Bean是DummyPing
  • C的上下文,使用default.RibbonClientSpecification  规范创建,IPing 对应的Bean是DummyPing


RibbonClientConfiguration

SpringClientFactory 初始化向其父类,传递RibbonClientConfiguration配置类做为RibbonClient默认的配置。

public SpringClientFactory() {
    super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
  }
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
      String propertyName) {
    this.defaultConfigType = defaultConfigType;
    this.propertySourceName = propertySourceName;
    this.propertyName = propertyName;
}

RibbonClientConfiguration 配置类中注册的就是Ribbon 默认的组件

image.png


EurekaRibbonClientConfiguration

在与Eureka一起使用的时候,RibbonEurekaAutoConfiguration  使用@RibbonClients注解引入EurekaRibbonClientConfiguration配置类对RibbonClient默认配置的部分组件进行覆盖。

@Configuration
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {
}

EurekaRibbonClientConfiguration 配置类会覆盖:

  • DiscoveryEnabledNIWSServerList 替换 ribbonServerList , 默认安装一个DomainExtractingServerList代理DiscoveryEnabledNIWSServerList
  • NIWSDiscoveryPing 替换 (IPing) DummyPing


RibbonLoadBalancerClient

RibbonLoadBalancerClient 就是负载均衡客户端了。

通过此客户端,我们可以传入服务id,从springClientFactory选择出对应配置的上下文。使用适用于当前服务的负载均衡组件集,来实现负载均衡的目的。

@Bean
  @ConditionalOnMissingBean(LoadBalancerClient.class)
  public LoadBalancerClient loadBalancerClient() {
    return new RibbonLoadBalancerClient(springClientFactory());
  }


负载均衡原理


路由与负载

LoadBalancerClient

RibbonLoadBalancerClient#choose方法。通过传入服务名,从多个副本中找出一个服务,以达到负载均衡的目的。

@Override
  public ServiceInstance choose(String serviceId) {
    Server server = getServer(serviceId);
    if (server == null) {
      return null;
    }
    return new RibbonServer(serviceId, server, isSecure(server, serviceId),
        serverIntrospector(serviceId).getMetadata(server));
  }
protected Server getServer(String serviceId) {
    return getServer(getLoadBalancer(serviceId));
}
protected Server getServer(ILoadBalancer loadBalancer) {
    if (loadBalancer == null) {
      return null;
    }
    return loadBalancer.chooseServer("default"); // TODO: better handling of key
}

RibbonLoadBalancerClient#choose 方法调用loadBalancer.chooseServer

ILoadBalancer: 负载均衡器

从工厂内获取负载均衡器,上文配置类说过此处的Bean 是ZoneAwareLoadBalancer

protected ILoadBalancer getLoadBalancer(String serviceId) {
    return this.clientFactory.getLoadBalancer(serviceId);
}
复制代码

ZoneAwareLoadBalancer#chooseServer方法

ZoneAwareLoadBalancer
public Server chooseServer(Object key) {
。。。
//默认一个区域的情况下直接调用父类的chooseServer(key)
 if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if (zone != null) {
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    server = zoneLoadBalancer.chooseServer(key);
                }
 }
 。。。。
}
BaseLoadBalancer
public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
}


IRule: 负载均衡策略

rule.choose(key) 根据策略从服务列表中选择一个出来。

默认的IRule是ZoneAvoidanceRule。

choose 方法在其父类PredicateBasedRule中

public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }

可以看出先执行 ILoadBalancer#getAllServers 获取所有服务,传入的策略执行方法中,选择一个服务。


获取与更新服务

那么ILoadBalancer.allServerList是如何存储所有服务的呢?


ServerListUpdater: 服务更新

ZoneAvoidanceRule的直接父类DynamicServerListLoadBalancer:

在初始化属性时,会初始化UpdateAction 属性。UpdateAction   是一个ServerListUpdater的一个内部接口,此处初始化了一个匿名实现类。

protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate() {
            updateListOfServers();//更新服务列表。
        }
    };

在初始化构造方法时:默认创建(ServerListUpdater)PollingServerListUpdater()

并且调用restOfInit(clientConfig),接着调用enableAndInitLearnNewServersFeature(); 方法。

public void enableAndInitLearnNewServersFeature() {
        LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
        serverListUpdater.start(updateAction);//执行updateAction动作。
}

最终在PollingServerListUpdater中会有一个定时调度,此定时调度会定时执行UpdateAction 任务,来更新服务列表。默认会在任务创建后1秒后开始执行,并且上次执行完成与下次执行开始之间的间隔,默认30秒。

scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
);


ServerList 服务列表

UpdateAction#doUpdate() 会调用updateListOfServers() 执行服务列表的更新。

@VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
            servers = serverListImpl.getUpdatedListOfServers();//获取所有服务列表
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);
            if (filter != null) {
                servers = filter.getFilteredListOfServers(servers);//过滤服务列表
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
            }
        }
        updateAllServerList(servers);
    }

此时serverListImpl 实现类是DiscoveryEnabledNIWSServerListDiscoveryEnabledNIWSServerList#getUpdatedListOfServers 执行obtainServersViaDiscovery方法

private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
 //获取 DiscoveryClient   
 EurekaClient eurekaClient = (EurekaClient)this.eurekaClientProvider.get();
 //获取服务列表          
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion);
                    ....
}

eurekaClientProvider其实就是对DiscoveryManager的一个代理。DiscoveryManager 在Eureka客户端的秘密说过,就是对DiscoveryClient的管理者。

eurekaClient.getInstancesByVipAddress 最终调用DiscoveryClient.localRegionApps获取服务列表。

DiscoveryClient.localRegionApps 在Eureka客户端的秘密已经说过是客户端服务列表的缓存。

从此,我们也可以看出,Ribbon在与Eureka一起使用时,是从DiscoveryClient获取服务列表的。


ServerListFilter 服务列表过滤

updateListOfServers 方法中获取到服务列表后,并没有直接返回,而是通过 ServerListFilter进行了过滤

此时默认的是ZonePreferenceServerListFilter ,会过滤出同区域的服务实例, 也就是区域优先

servers = filter.getFilteredListOfServers(servers);


IPing: 检查服务状态

updateListOfServers 方法中执行完过滤后,最后还做了一个操作updateAllServerList。

updateAllServerList(servers);
 protected void updateAllServerList(List<T> ls) {
        // other threads might be doing this - in which case, we pass
        if (serverListUpdateInProgress.compareAndSet(false, true)) {
            try {
                for (T s : ls) {
                    s.setAlive(true); // set so that clients can start using these
                }
                setServersList(ls);
                super.forceQuickPing();
            } finally {
                serverListUpdateInProgress.set(false);
            }
        }
    }

updateAllServerList 中最终的一步,就是ping操作,用于检测服务时候存活。此时默认是DummyPing ,

public boolean isAlive(Server server) {
        return true;//默认永远是存活状态。
    }

Ping任务其实是有一个定时任务存在的:

BaseLoadBalancer 负载均衡器,在初始化时会创建一个定时任务NFLoadBalancer-PingTimer-以10秒的间隔定时去执行Ping任务

public BaseLoadBalancer() {
        this.name = DEFAULT_NAME;
        this.ping = null;
        setRule(DEFAULT_RULE);
        setupPingTask();
        lbStats = new LoadBalancerStats(DEFAULT_NAME);
    }

至此: Ribbon负载均衡的工作原理轮廓就展现出来了, 因为本文的目的在于阐述Ribbon的工作原理。具体向IRule 的具体策略细节,不在本文范围内,以后找机会再说。


总结


当Ribbon与Eureka一起使用时,Ribbon会从Eureka客户端的缓存中取服务列表。

我们在使用Ribbon的时候,并没有直接使用RibbonLoadBalancerClient ,而是常用Resttemplate+@LoadBalanced来发送请求,那@LoadBalanced是如何让Resttemplate 具有负载均衡的能力的呢?


相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
12天前
|
负载均衡 算法 Java
Spring Cloud全解析:负载均衡算法
本文介绍了负载均衡的两种方式:集中式负载均衡和进程内负载均衡,以及常见的负载均衡算法,包括轮询、随机、源地址哈希、加权轮询、加权随机和最小连接数等方法,帮助读者更好地理解和应用负载均衡技术。
|
1月前
|
负载均衡 算法 架构师
Ribbon负载均衡
上一节就已经实现的负载均衡笔者并未深入探讨,本节通过分析负载均衡算法、Ribbon实现负载均衡的底层原理和实现过程,让大家对负载均衡有了一个大体认识,同时针对Ribbon自定义负载均衡策略,饥饿加载让大家对于Ribbon的了解又多一些。Ribbon实现的负载均衡只是方案之一,我们可以尽量多了解但不要局限于此。
|
8天前
|
负载均衡 Java Nacos
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
微服务介绍、SpringCloud、服务拆分和远程调用、Eureka注册中心、Ribbon负载均衡、Nacos注册中心
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
|
1月前
|
存储 设计模式 缓存
OpenFeign集成Ribbon负载均衡-过滤和选择服务核心实现
该文章主要介绍了如何在OpenFeign中集成Ribbon以实现负载均衡,并详细分析了Ribbon中服务选择和服务过滤的核心实现过程。文章还涉及了Ribbon中负载均衡器(ILoadBalancer)和负载均衡策略(IRule)的初始化方式。
OpenFeign集成Ribbon负载均衡-过滤和选择服务核心实现
|
1月前
|
缓存 负载均衡 Java
OpenFeign最核心组件LoadBalancerFeignClient详解(集成Ribbon负载均衡能力)
文章标题为“OpenFeign的Ribbon负载均衡详解”,是继OpenFeign十大可扩展组件讨论之后,深入探讨了Ribbon如何为OpenFeign提供负载均衡能力的详解。
OpenFeign最核心组件LoadBalancerFeignClient详解(集成Ribbon负载均衡能力)
|
26天前
|
负载均衡 算法 Java
SpringCloud之Ribbon使用
通过 Ribbon,可以非常便捷的在微服务架构中实现请求负载均衡,提升系统的高可用性和伸缩性。在实际使用中,需要根据实际场景选择合适的负载均衡策略,并对其进行适当配置,以达到更佳的负载均衡效果。
31 13
|
2月前
|
负载均衡 算法 网络协议
Ribbon 负载均衡源码解读
Ribbon 负载均衡源码解读
49 15
Ribbon 负载均衡源码解读
|
2月前
|
负载均衡 Java Spring
Spring cloud gateway 如何在路由时进行负载均衡
Spring cloud gateway 如何在路由时进行负载均衡
290 15
|
2月前
|
负载均衡 Java API
Feign 进行rpc 调用时使用ribbon负载均衡源码解析
Feign 进行rpc 调用时使用ribbon负载均衡源码解析
60 11
|
3月前
|
缓存 负载均衡 算法
解读 Nginx:构建高效反向代理和负载均衡的秘密
解读 Nginx:构建高效反向代理和负载均衡的秘密
92 2