<番外篇>spring-cloud Hoxton.SR2 负载均衡ribbon自动装配,负载均衡部分源码解析
1、找到自动装配的主入口;
ribbon的构建,是基于eureka上的,使用ribbon,只需要引入spring-cloud-starter-netflix-ribbon
的包就可以了;
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
首先,在项目找到spring-cloud-starter-netflix-ribbon
的jar包,看到里面并没有代码,而是引入了spring-cloud-netflix-ribbon
的jar包,如图所示:
另外还有一个jar包,也很重要,就是cloud的基础包spring-cloud-starter
,后面会提到。
找到spring-cloud-starter-netflix-ribbon
包的spring.factories
文件;
查看文件内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
可以看到,默认ribbon自动装配的配置类是org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
;在对应包下,找到这个类文件,内容如下所示:
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.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;
@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());
}
@Bean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(
final SpringClientFactory clientFactory) {
return new RibbonLoadBalancedRetryFactory(clientFactory);
}
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
return new PropertiesFactory();
}
@Bean
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
return new RibbonApplicationContextInitializer(springClientFactory(),
ribbonEagerLoadProperties.getClients());
}
@Configuration
@ConditionalOnClass(HttpRequest.class)
@ConditionalOnRibbonRestClient
protected static class RibbonClientHttpRequestFactoryConfiguration {
@Autowired
private SpringClientFactory springClientFactory;
@Bean
public RestTemplateCustomizer restTemplateCustomizer(
final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
return restTemplate -> restTemplate
.setRequestFactory(ribbonClientHttpRequestFactory);
}
@Bean
public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
return new RibbonClientHttpRequestFactory(this.springClientFactory);
}
}
// TODO: support for autoconfiguring restemplate to use apache http client or okhttp
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnRibbonRestClientCondition.class)
@interface ConditionalOnRibbonRestClient {
}
private static class OnRibbonRestClientCondition extends AnyNestedCondition {
OnRibbonRestClientCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@Deprecated // remove in Edgware"
@ConditionalOnProperty("ribbon.http.client.enabled")
static class ZuulProperty {
}
@ConditionalOnProperty("ribbon.restclient.enabled")
static class RibbonProperty {
}
}
/**
* {@link AllNestedConditions} that checks that either multiple classes are present.
*/
static class RibbonClassesConditions extends AllNestedConditions {
RibbonClassesConditions() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnClass(IClient.class)
static class IClientPresent {
}
@ConditionalOnClass(RestTemplate.class)
static class RestTemplatePresent {
}
@ConditionalOnClass(AsyncRestTemplate.class)
static class AsyncRestTemplatePresent {
}
@ConditionalOnClass(Ribbon.class)
static class RibbonPresent {
}
}
}
2、对自动装配类源码分析
2.1、类上注解分析
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
ServerIntrospectorProperties.class })
@Configuration
:表示一个配置类
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
:本类装配成功的先决条件
查看这个内部类,实例化的条件,必须要有IClient.class
,RestTemplate.class
,AsyncRestTemplate.class
,Ribbon.class
的类;
/**
* {@link AllNestedConditions} that checks that either multiple classes are present.
*/
static class RibbonClassesConditions extends AllNestedConditions {
RibbonClassesConditions() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnClass(IClient.class)
static class IClientPresent {
}
@ConditionalOnClass(RestTemplate.class)
static class RestTemplatePresent {
}
@ConditionalOnClass(AsyncRestTemplate.class)
static class AsyncRestTemplatePresent {
}
@ConditionalOnClass(Ribbon.class)
static class RibbonPresent {
}
}
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
:eureka这个自动装配类再本类装配完后,再执行eureka的装配;就是ribbon实例启动成功后,才会向eureka中注入本服务。
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,AsyncLoadBalancerAutoConfiguration.class})
:本类实例化之前,要先执行LoadBalancerAutoConfiguration和AsyncLoadBalancerAutoConfiguration的自动装配;这里很重要,注入了同步负载均衡器和异步的负载均衡器,后面的代码中会用到。
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,ServerIntrospectorProperties.class})
:使这两个配置类,实例化(生效)。
2.2、类中装配的实例
@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());
}
@Bean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(
final SpringClientFactory clientFactory) {
return new RibbonLoadBalancedRetryFactory(clientFactory);
}
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
return new PropertiesFactory();
}
@Bean
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
return new RibbonApplicationContextInitializer(springClientFactory(),
ribbonEagerLoadProperties.getClients());
}
从上往下看
2.2.1、HasFeatures:不知道暂时不知道什么作用,过滤掉;
2.2.2、SpringClientFactory:这个工厂类,为容器中注入了比较多组建,我们详细看一下
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
}
SpringClientFactory的类,构造方法,为父级,准备了一个RibbonClientConfiguration
的类主要的一些方法,就是获取容器中的实例,只拿了一个例子,如下:
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
SpringClientFactory继承自NamedContextFactory,我们看NamedContextFactory的代码,如下:
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
DisposableBean
接口:销毁方法
ApplicationContextAware
:这个接口,可以拿到ApplicationContext对象
主要的方法就一个getInstance,以及getInstance的重载方法,就是通过ApplicationContext对象,获取对象的实例;
@SuppressWarnings("unchecked")
public <T> T getInstance(String name, ResolvableType type) {
AnnotationConfigApplicationContext context = getContext(name);
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type);
if (beanNames.length > 0) {
for (String beanName : beanNames) {
if (context.isTypeMatch(beanName, type)) {
return (T) context.getBean(beanName);
}
}
}
return null;
}
2.2.3、LoadBalancerClient:负载均衡器,主要的操作就是这个;我们看这个的源码,为spring容器中注入了哪些可以用的业务或者组件
主要方法
choose:选择器;
execute:执行器;、
getServer:获取服务;
我们主要看下execute执行器的方法,ribbon的最终执行,就是调用execute方法来执行的;
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException {
//获取Server的实例
Server server = null;
if (serviceInstance instanceof RibbonServer) {
server = ((RibbonServer) serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
//获取ribbon的负载均衡器配置
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
//状态记录器
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
//执行,并且设置状态
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
步骤,我在代码中,写了注释,主要看这一步
//获取ribbon的负载均衡器配置
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
调用最上面实例化的SpringClientFactory中的getLoadBalancerContext方法,再调用getInstance,在getInstance里,再调用父级的super.getContext(name);
public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
return getInstance(serviceId, RibbonLoadBalancerContext.class);
}
@Override
public <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type);
if (instance != null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
@Override
protected AnnotationConfigApplicationContext getContext(String name) {
return super.getContext(name);
}
我们再看父级NamedContextFactory.class的getContext方法,调用了下面的createContext方法。
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//为容器中注入实例
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
//为容器中,注入this.defaultConfigType
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
主要看这一步,为容器中注入的this.defaultConfigType
//为容器中,注入this.defaultConfigType
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
this.defaultConfigType 就是SpringClientFactory构造方法中的RibbonClientConfiguration.class
,上述有提到过;
剩下的,我们看下RibbonClientConfiguration.class
这个类中,主要配置了ribbon的负载均衡算法
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
这个就说明,我们在使用ribbon的时候,切换负载均衡算法,主需要为容器中配置一个IRule
接口的实现的实例就可以了;
BestAvailableRule:表示请求数最少策略;
PredicateBaseRule:表示过滤掉一些一直连接失败的服务,或者并发高的服务;先过滤再轮询的策略
RandomRule:表示随机策略;
RetryRule:当前请求超时,那么就再次轮询调用下一个请求,直到成功;
RoundRobinRule:轮询策略;轮询策略下有个加权策略:WeightedResponseTimeRule;另外一个(ResponseTimeWeightedRule)不建议使用了,名字跟WeightedResponseTimeRule差不多,只是单次排列不一样;
ribbon切换默认的负载均衡算法
@Configuration
public class RestConfig {
@Bean
@LoadBalanced // Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Bean
public IRule myRule() {
// return new RoundRobinRule();
return new RandomRule();// 达到的目的,用我们重新选择的随机算法替代默认的轮询。
// return new RetryRule();
}
}
向spring容器中,注入一个IRule的实例就可以了;
2.2.4、后面的,就不说了,结束了,主要也就是知道ribbon装配了什么,怎么切换负载均衡算法,就可以了,大家有兴趣的,可以再继续看下去,再远程调用的时候,debug模式,会看到更详细。
具体代码信息,可以查看《码云》