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

关联阅读:

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

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

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

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

配置文件

同其余微服务组件与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());
	}
	.......
	.......
}
复制代码

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

RibbonClientSpecification:

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

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

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

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

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

这样就获得了RibbonClientSpecification 规范列表。post

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 默认的组件

在这里插入图片描述

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 实现类是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获取服务列表的。

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 具备负载均衡的能力的呢?

看下篇文章