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 具有负载均衡的能力的呢?
看下篇文章
欢迎大家关注我的公众号【源码行动】,最新个人理解及时奉送。