同其他微服务组件与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()); } ....... ....... } 复制代码
下面讲讲配置文件中所包含的知识点。
RibbonClient规范,一个规范就对应一种类型的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属性,用来替换所有非自定义的客户端的默认组件
每个微服务都在调用多个微服务。调用不同微服务的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。 那么会创建三个上下文:
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 默认的组件
在与Eureka一起使用的时候,RibbonEurekaAutoConfiguration 使用 @RibbonClients
注解引入EurekaRibbonClientConfiguration配置类对RibbonClient默认配置的部分组件进行覆盖。
@Configuration @EnableConfigurationProperties @ConditionalOnRibbonAndEurekaEnabled @AutoConfigureAfter(RibbonAutoConfiguration.class) @RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class) public class RibbonEurekaAutoConfiguration { } 复制代码
EurekaRibbonClientConfiguration 配置类会覆盖:
RibbonLoadBalancerClient 就是负载均衡客户端了。
通过此客户端,我们可以传入服务id,从springClientFactory选择出对应配置的上下文。使用适用于当前服务的负载均衡组件集,来实现负载均衡的目的。
@Bean @ConditionalOnMissingBean(LoadBalancerClient.class) public LoadBalancerClient loadBalancerClient() { return new RibbonLoadBalancerClient(springClientFactory()); } 复制代码
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
从工厂内获取负载均衡器,上文配置类说过此处的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; } } } 复制代码
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是如何存储所有服务的呢?
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 ); 复制代码
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 实现类是 DiscoveryEnabledNIWSServerList
DiscoveryEnabledNIWSServerList#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获取服务列表的。
updateListOfServers 方法中获取到服务列表后,并没有直接返回,而是通过 ServerListFilter进行了过滤
此时默认的是ZonePreferenceServerListFilter ,会过滤出同区域的服务实例, 也就是区域优先
servers = filter.getFilteredListOfServers(servers); 复制代码
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 的具体策略细节,不在本文范围内,以后找机会再说。