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
RibbonClient规范,一个规范就对应一种类型的RibbonClient。 规范怎么制订呢?ide
此两个注解都会引入一个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属性,用来替换全部非自定义的客户端的默认组件
每一个微服务都在调用多个微服务。调用不一样微服务的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 的具体策略细节,不在本文范围内,之后找机会再说。
当Ribbon与Eureka一块儿使用时,Ribbon会从Eureka客户端的缓存中取服务列表。
咱们在使用Ribbon的时候,并无直接使用RibbonLoadBalancerClient ,而是经常使用Resttemplate+@LoadBalanced来发送请求,那@LoadBalanced是如何让Resttemplate 具备负载均衡的能力的呢?
看下篇文章