Springcloud源码阅读4:Ribbon客户端负载均衡(下)

关联阅读:

SpringCloud源码阅读0-SpringCloud必备知识
SpringCloud源码阅读1-Eureka服务端的秘密
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 默认的组件
在这里插入图片描述

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 具有负载均衡的能力的呢?
看下篇文章


欢迎大家关注我的公众号【源码行动】,最新个人理解及时奉送。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值