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

简介: 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 具有负载均衡的能力的呢?


相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
3天前
|
负载均衡 算法 Java
Ribbon自定义负载均衡算法
Ribbon自定义负载均衡算法
12 1
|
10天前
|
负载均衡
Ribbon负载均衡策略
Ribbon负载均衡策略
|
15天前
|
负载均衡 算法
SpringCloud&Ribbon负载均衡原理与实践
SpringCloud&Ribbon负载均衡原理与实践
20 3
|
28天前
|
负载均衡 算法 Java
Ribbon的负载均衡策略
Ribbon的负载均衡策略
36 2
|
1月前
|
负载均衡 网络协议 Java
构建高效可扩展的微服务架构:利用Spring Cloud实现服务发现与负载均衡
本文将探讨如何利用Spring Cloud技术实现微服务架构中的服务发现与负载均衡,通过注册中心来管理服务的注册与发现,并通过负载均衡策略实现请求的分发,从而构建高效可扩展的微服务系统。
|
5月前
|
JSON 负载均衡 Java
Spring Cloud Ribbon:负载均衡的服务调用
Spring Cloud Ribbon:负载均衡的服务调用
67 0
|
7月前
|
负载均衡
09SpringCloud - Ribbon项目示例
09SpringCloud - Ribbon项目示例
18 0
|
2月前
|
负载均衡 算法 Java
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(四)Ribbon的使用
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(四)Ribbon的使用
27 0
|
2月前
|
负载均衡
【二十】搭建SpringCloud项目四(Ribbon)
【二十】搭建SpringCloud项目四(Ribbon)
21 0
|
2月前
|
存储 负载均衡 Java
【Spring底层原理高级进阶】微服务 Spring Cloud 的注册发现机制:Eureka 的架构设计、服务注册与发现的实现原理,深入掌握 Ribbon 和 Feign 的用法 ️
【Spring底层原理高级进阶】微服务 Spring Cloud 的注册发现机制:Eureka 的架构设计、服务注册与发现的实现原理,深入掌握 Ribbon 和 Feign 的用法 ️