从这几篇源码解读可以发现一个规律,那就是引进某个服务,会经常看到@Configuration和@Import(核心服务注册类)这两个注解,伴随着@ConditonalOnClass(XXX.class)、@ConditionalOnBean(XXX.class)、@ConditionOnProperty(***)和@ConditionalOnMissingBean(XXX.class)。作为Spring Boot神奇的特性-简化配置,@Configuration起了很大的作用,此处做个伏笔,感兴趣的可以先阅读JavaDoc,关于@Configuration的详细解释会在之后的Spring Boot系列文章中阐明。按照惯例,此处从核心服务类开始展开对Ribbon的源码解读
@Configuration
@Import(RibbonClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
String value() default "";
// 配置Ribbon客户端名字
String name() default "";
// Ribbon客户端的自定义配置,可以配置生成客户端的各个组件,比如说ILoadBalancer、ServerListFilter和IRule。默认的配置为RibbonClientConfiguration.java
Class>[] configuration() default {};
}
RibbonClientConfigurationRegistrar.class实现了接口ImportBeanDefinitionRegistrar,这个接口是Spring动态注册BeanDefinition的接口,用来注册Ribbon所需要的BeanDefinition
我们看下接口ImportBeanDefinitionRegistrar的描述,处理被注解@Configuration标注的类时用来标记bean definition,说白了就是这个接口用来实现动态注入,可以通过下面的小Demo进行Debug,一步一步看下代码Spring Boot做了什么
Interface to be implemented by types that register additional bean definitions when processing @{@link Configuration} classes. Useful when operating at the bean definition level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
小Demo,案例来自Spring - Using ImportBeanDefinitionRegistrar
package thinking.in.spring.boot;
/**
* @Author: Bruce
* @Date: 2019/7/10 0:18
* @Version 1.0
*/
public class AppBean {
private String str;
public void setStr(String str) {
this.str = str;
}
public void process() {
System.out.println(str);
}
}
package thinking.in.spring.boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import thinking.in.spring.boot.MyBeanRegistrar;
import thinking.in.spring.boot.bean.ClientBean;
/**
* @Author: Bruce
* @Date: 2019/7/10 0:19
* @Version 1.0
*/
@Configuration
@Import(MyBeanRegistrar.class)
public class MyConfig {
@Bean
ClientBean clientBean() {
return new ClientBean();
}
}
package thinking.in.spring.boot.bean;
import org.springframework.beans.factory.annotation.Autowired;
import thinking.in.spring.boot.AppBean;
/**
* @Author: Bruce
* @Date: 2019/7/10 0:20
* @Version 1.0
*/
public class ClientBean {
@Autowired
private AppBean appBean;
public void doSomething() {
appBean.process();
}
}
package thinking.in.spring.boot;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @Author: Bruce
* @Date: 2019/7/10 0:14
* @Version 1.0
*/
public class MyBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
GenericBeanDefinition gbd = new GenericBeanDefinition();
gbd.setBeanClass(AppBean.class);
gbd.getPropertyValues().addPropertyValue("str", "value set from registrar");
registry.registerBeanDefinition("AppBean", gbd);
}
}
package thinking.in.spring.boot;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import thinking.in.spring.boot.bean.ClientBean;
import thinking.in.spring.boot.config.MyConfig;
/**
* @Author: Bruce
* @Date: 2019/7/10 0:21
* @Version 1.0
*/
public class ImportBeanDefinitionRegistrarExample {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
ClientBean bean = applicationContext.getBean(ClientBean.class);
bean.doSomething();
}
}
通过上面的小Demo相信可以粗略了解到实现接口ImportBeanDefinitionRegistrar的作用,那么接下来就通过源码分析Ribbon又是如何具体实现
RibbonClientConfigurationRegistrar.class
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 获取@RibbonClient的参数值,获取clientName后进行configuration的注册
Map attrs = metadata
.getAnnotationAttributes(RibbonClients.class.getName(), true);
if (attrs != null && attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
for (AnnotationAttributes client : clients) {
// 向BeanDefinitionRegistry注册一个RibbonClientSpecification的BeanDefinition
// getClientName()获取RibbonClient的value或者name数值
registerClientConfiguration(registry, getClientName(client),
client.get("configuration"));
}
}
if (attrs != null && attrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
attrs.get("defaultConfiguration"));
}
Map client = metadata
.getAnnotationAttributes(RibbonClient.class.getName(), true);
String name = getClientName(client);
if (name != null) {
registerClientConfiguration(registry, name, client.get("configuration"));
}
}
在方法 registerBeanDefinitions()中,我们看到了调用registerClientConfiguration()方法来注册,那么注册了什么,跟进代码可以看到,使用构造器的设计模式构建了BeanDefinition,然后在注册表registry(beanDefinitionMap,实际上是一个ConcurrentHashMap的容器)中添加key(也就是beanName)为客户端名.RibbonClientSpecification,value为对应的Bean Definition。RibbonClientSpecification是NameContextFactory.Specification的实现类,供SpringClientFactory使用,而RibbonAutoConfiguration里会进行SpringClientFactory实例的初始化
RibbonClientConfigurationRegistrar.class
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(RibbonClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 向BeanDefinitionRegistry注册一个RibbonClientSpecification
registry.registerBeanDefinition(name + ".RibbonClientSpecification",
builder.getBeanDefinition());
}
BeanDefinitionBuilder.class
public static BeanDefinitionBuilder genericBeanDefinition(Class> beanClass) {
// 通过Builder模式创建BeanDefinitionBuilder
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
builder.beanDefinition.setBeanClass(beanClass);
return builder;
}
Spring Boot框架有个特性,就是利用SPI来做自动化配置,对Ribbon组件,可以在文件spring.factories中看到它的自动配置类是org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
Spring Cloud作为搭建在Spring Boot之上的生态,更是沿用了这个编程习惯。了解Spring Boot框架就会发现,Spring Boot会有一个spring.factories的文件,里面配置了许多的配置类和监听类等,这样就可以和各个组件一起使用,但是又不必耦合在一起,算是SPI的典型应用,这也是Spring Boot经典的自动配置特性。事实上这个特性就是留给开发人员去开发自定义自动装配类,Spring Cloud Ribbon就是对Ribbon的Spring Boot改造,新增了Spring Boot的特性。由于本篇文章重点是关于Ribbon的源码,大家可以阅读下篇官方文章了解一二。
49. Creating Your Own Auto-configuration
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration.class
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
// LoadBalancerAutoConfiguration.class和AsyncLoadBalancerAutoConfiguration.class会在本配置类前执行
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
@Autowired(required = false)
// RibbonClientConfigurationRegistrar中注册的RibbonClientSpecification会被注入到configurations里
private List configurations = new ArrayList<>();
@Autowired
private RibbonEagerLoadProperties ribbonEagerLoadProperties;
@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() {
// 创建新的RibbonLoadBalancerClient实例,传入配置好的SpringClientFactory,最终会调用SpringClientFactory.getLoadBalancerContext()方法来获得实例
return new RibbonLoadBalancerClient(springClientFactory());
}
}
此处可以看到方法loadBalancerClient()返回一个新的Ribbon负载客户端,那么客户端跟负载均衡是什么关系,跟进代码看下
首先看下接口LoadBalancerClient
public interface LoadBalancerClient extends ServiceInstanceChooser {
T execute(String serviceId, LoadBalancerRequest request) throws IOException;
T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest request) throws IOException;
// 下面这段话点明了可以使用服务名来调用网络请求,Ribbon会自动将其转换为host:port格式,这也是我在实战篇中提到的使用服务名来调用网络请求,避免host的硬编码,可见开发人员设计之初的思维还是挺缜密的
/**
* Creates a proper URI with a real host and port for systems to utilize. Some systems
* use a URI with the logical service name as the host, such as
* http://myservice/path/to/service. This will replace the service name with the
* host:port from the ServiceInstance.
* @param instance service instance to reconstruct the URI
* @param original A URI with the host as a logical service name.
* @return A reconstructed URI.
*/
URI reconstructURI(ServiceInstance instance, URI original);
}
可以看到接口LoadBalancerClient还继承了接口ServiceInstanceChooser,跟进去看下,从描述可以知道choose()方法会根据服务Id从服务器列表中选择相应的服务实例,那么具体是如何实现的,跟进实现类RibbonLoadBalancerClient.class一探究竟
public interface ServiceInstanceChooser {
/**
* Chooses a ServiceInstance from the LoadBalancer for the specified service.
* @param serviceId The service ID to look up the LoadBalancer.
* @return A ServiceInstance that matches the serviceId.
*/
ServiceInstance choose(String serviceId);
}
RibbonLoadBalancerClient.class
@Override
public ServiceInstance choose(String serviceId) {
return choose(serviceId, null);
}
public ServiceInstance choose(String serviceId, Object hint) {
// 获取服务器
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
// 将服务器封装成RibbonServer
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
public T execute(String serviceId, LoadBalancerRequest request, Object hint)
throws IOException {
// 每次发送请求都会获得ILoadBalancer
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
从下图可知ZoneAwareLoadBalancer是Spring Cloud提供ILoadBalancer的默认实例,代码org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration类中也可以看出各个Bean 的默认实现,例如ZoneAwareLoadBalancer为ILoadBalancer的默认实现,又或者com.netflix.client.config.DefaultClientConfigImpl
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList serverList, ServerListFilter serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
// 传入参数IClientConfig、IRule、IPing、ServerList、ServerListFilter、ServerListUpdater
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
DummyPing为IPing的默认实现
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing();
}
那么我们分析在这个类是如何实现负载均衡的,
从ZoneAwareLoadBalancer的构造函数可以知道它传进去了许多负载均衡的配置类,跟进代码看下具体是如何实现的
ZoneAwareLoadBalancer.class
@Override
public Server chooseServer(Object key) {
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
Server server = null;
try {
// 获取当前有关负载均衡的服务器状态
LoadBalancerStats lbStats = getLoadBalancerStats();
Map zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
if (triggeringLoad == null) {
// 获取平均负载的阈值
triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
}
if (triggeringBlackoutPercentage == null) {
// 获取平局实例故障率的阈值
triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
}
// 根据两个阈值来获取所有可用的服务区列表
Set availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
// 随机从可用的服务区列表中选择一个服务区
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
// 得到zone对应的BaseLoadBalancer
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
if (server != null) {
return server;
} else {
logger.debug("Zone avoidance logic is not invoked.");
return super.chooseServer(key);
}
}
可以知道通过方法ZoneAvoidanceRule.getAvailableZones()来获取可用服务区列表,那么具体是如何实现的呢?
下图是实现的一个获取可用区域的流程图
注:图片来自Spring Cloud微服务架构进阶
ZoneAvoidanceRule.class
public static Set getAvailableZones(
Map snapshot, double triggeringLoad,
double triggeringBlackoutPercentage) {
if (snapshot.isEmpty()) {
return null;
}
Set availableZones = new HashSet(snapshot.keySet());
if (availableZones.size() == 1) {
return availableZones;
}
Set worstZones = new HashSet();
double maxLoadPerServer = 0;
boolean limitedZoneAvailability = false;
// 遍历所有的服务区来判定
for (Map.Entry zoneEntry : snapshot.entrySet()) {
String zone = zoneEntry.getKey();
ZoneSnapshot zoneSnapshot = zoneEntry.getValue();
// 判定该服务区中的服务实例数
int instanceCount = zoneSnapshot.getInstanceCount();
if (instanceCount == 0) {
// 如果服务区没有服务实例,删除该服务区
availableZones.remove(zone);
limitedZoneAvailability = true;
} else {
double loadPerServer = zoneSnapshot.getLoadPerServer();
if (((double) zoneSnapshot.getCircuitTrippedCount())
/ instanceCount >= triggeringBlackoutPercentage
|| loadPerServer < 0) {
// 实例故障率(断路器断开次数/实例数)大于等于阈值(阈值为0.99999)或者服务区实例平均负载小于0,删除该服务区
availableZones.remove(zone);
limitedZoneAvailability = true;
} else {
// 如果服务区的平均负载和最大负载小于0.000001d,则添加该服务区到worstZones(HashSet容器)集合中
if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
// they are the same considering double calculation
// round error
worstZones.add(zone);
} else if (loadPerServer > maxLoadPerServer) {
maxLoadPerServer = loadPerServer;
worstZones.clear();
worstZones.add(zone);
}
}
}
}
// 如果最大负载小于设置的阈值则直接返回
if (maxLoadPerServer < triggeringLoad && !limitedZoneAvailability) {
// zone override is not needed here
return availableZones;
}
String zoneToAvoid = randomChooseZone(snapshot, worstZones);
// 如果最大平均负载大于等于设定的阈值,随机从worstZones中剔除一个
if (zoneToAvoid != null) {
availableZones.remove(zoneToAvoid);
}
return availableZones;
}
至此,关于Ribbon的负载均衡实现就大略说这些。
推荐阅读:
Spring Cloud 微服务架构进阶
6. Client Side Load Balancer: Ribbon
Spring - Using ImportBeanDefinitionRegistrar