入口

我们在每次使用Spring Cloud Netflix Ribbon的时候,有一个固定的套路就是需要用@LoadBalanced注解修饰一个RestTemplate

1
2
3
4
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}

根据以往Spring Cloud集成套路,必然会有一个与之相关的自动配置类,果不其然,找到了一个LoadBalancerAutoConfiguration

1
2
3
4
5
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
  • ConditionalOnBean
    会依赖一个叫LoadBalancerClient的对象
  • LoadBalancerRetryProperties
    开启了一个配置,是Ribbon重试相关的

下面这行代码,比较灵活的运用了Spring原生注解的特性,作用就是把所有的被@LoadBalanced注解修饰的RestTemplate对象都收集到这个集合里。

1
2
3
4
5
class RibbonAutoConfiguration

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

其实现原理,玄机在@LoadBalanced中,可以看到该注解上被@Qulifier注解修饰,这就触发了Spring一个比较偏门的特性,Spring会把这个注解修饰的对象注册到Spring容器中去。

1
2
3
4
5
6
7
8
9
> @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
> @Retention(RetentionPolicy.RUNTIME)
> @Documented
> @Inherited
> @Qualifier // 注意这个
> public @interface LoadBalanced {
>
> }
>

有兴趣的朋友可以看看Spring处理@Qulifier的过程。QualifierAnnotationAutowireCandidateResolver#checkQualifier方法

接着,我们看到了Spring集成Ribbon关键的一步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class RibbonAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {

@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}

}
  • LoadBalancerInterceptor
    这是Spring与Ribbon代码的关键结合点。他是通过Spring为Ribbon定制的RestTemplate拦截器来实现的,在这个拦截器里,完成了将RestTemplate收到的请求委托给Ribbon处理的逻辑。

  • restTemplateCustomizer
    这里就是对Ribbon的RestTemplate拦截器加入到restTemplate中去。
    这一步的逻辑要结合着此处去看,一下子就明白了。原来Spring就是把被@LoadBalanced修饰的RestTemplate收集起来,然后一个个遍历,在每一个RestTemplate里追加一个为Ribbon定制的拦截器,完成了请求的委托处理,这算是Spring整合Ribbon最核心的逻辑之一了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class RibbonAutoConfiguration

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
    final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    return () -> restTemplateCustomizers.ifAvailable(customizers -> {
    for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
    for (RestTemplateCustomizer customizer : customizers) {
    customizer.customize(restTemplate);
    }
    }
    });
    }

Spring整合Ribbon的核心结合点

既然如此,我们就看看在为Ribbon定制的RestTemplate拦截器里具体完成了什么事情吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

private LoadBalancerClient loadBalancer;

private LoadBalancerRequestFactory requestFactory;

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {

final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();

return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}

}
  • LoadBalancerClient
    这个里面其实存放了Ribbon的几个核心组件,比如ILoadBalancer、IRule、RestClient、IPing等等,这个我们后面再仔细看,这里有个印象就好
  • LoadBalancerRequestFactory
    这里完成了遍历RestTemplate里的拦截器链,并一个个执行
  • loadBalancer.execute
    这里最最关键,完成了RestTemplate请求到Ribbon请求的转换,并且将请求委托给Ribbon处理

这段代码实现的非常优雅,不像我们之前分析Eureka源码的时候,都是大段大段的代码,看起来令人眼花缭乱,其最主要的槽点就是控制和业务逻辑没有做到很好的分离。人家Spring通过一系列组件的封装,控制逻辑,比如这里的请求转换,请求委托都清爽的呈现在我们眼前,让人一眼就知道Spring干了什么。如果想更详细的了解具体的处理逻辑,只要看那个组件的很少的代码就可以了,非常的内聚,我们要向这种代码风格学习

看看到底负载均衡器是如何创建的

我们上面已经分析了Spring集成Ribbon的核心骨架代码的一部分了,剩下的其实都是比较零散,也可以说非常内聚的点了,逐个击破即可。
其实在RibbonAutoConfiguration中还有一个关键的对象被创建了

1
2
3
4
5
6
7
8
class RibbonAutoConfiguration

@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}

我们看到他的方法列表,就会倍感熟悉,这ILoadBalancer不就是负载均衡器吗?

1
2
3
4
5
6
7
8
class SpringClientFactory

public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}

IClient<?, ?>> C getClient
ILoadBalancer getLoadBalancer

我们此时先按捺住激动的心情,先看看构造器里的RibbonClientConfiguration是个什么东东吧。

根据笔者阅读Spring集成代码的经验,涉及到三方技术核心组件创建的这样重要逻辑,Spring往往是在xxConfiguration里完成的。

1
2
3
4
5
6
7
8
9
10
11
12
class RibbonClientConfiguration 

@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;
}
  • 我们的选择是没有白费,看看这是什么,这里构建了一个IRule的实现ZoneAvoidanceRule,Ribbon组件+1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class RibbonClientConfiguration 

@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
}

@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
return new PollingServerListUpdater(config);
}
  • ServerList
    服务列表,Ribbon的核心概念

  • ServerListUpdater
    服务列表更新器,我们知道Ribbon的服务列表肯定是来自于Eureka,这个更新器的作用就不难猜测了
    PollingServerListUpdater,定时从Eureka那边拉取服务列表,至此,Spring集成Ribbon的所有核心逻辑都已经看到了

    这里不得不再夸赞一下Spring的代码水平,非常的高内聚,上一个是静态的服务列表,紧挨着就是服务列表的动态更新。
    这种代码对阅读者实在是太爽了,是一种享受。

1
2
3
4
5
6
7
8
9
10
11
12
13
class RibbonClientConfiguration 

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
  • 来了来了,我们本篇也可以拉下帷幕了
    此处就是ILoadBalancer负载均衡器的创建逻辑
    可以看到是用的ZoneAwareLoadBalancer这个实现类

    1
    2
    3
    class RibbonClientConfiguration 

    static class OverrideRestClient extends RestClient
  • OverrideRestClient,Ribbon的核心RestClient实现类
    通过引用分析,可以知道这个OverrideRestClient就是Spring提供的默认RestClient实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class RestClientRibbonConfiguration

    @Bean
    @Lazy
    @ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
    public RestClient ribbonRestClient(IClientConfig config, ILoadBalancer loadBalancer,
    ServerIntrospector serverIntrospector, RetryHandler retryHandler) {
    RestClient client = new RibbonClientConfiguration.OverrideRestClient(config,
    serverIntrospector);
    client.setLoadBalancer(loadBalancer);
    client.setRetryHandler(retryHandler);
    return client;
    }

总结

阅读Spring的代码是令人愉悦的,写法非常的高内聚,相关的逻辑紧紧的放在一处,生怕你看漏了。低耦合,控制与业务逻辑分离,非常清爽。

此处的结论在我们当前的视角是合乎逻辑的,但实际上与这个结果是有一些出入的,我们在后续的篇幅里再行探讨。

  • Spring提供的默认负载均衡器是ZoneAwareLoadBalancer
  • Spring提供的默认负载均衡规则是ZoneAvoidanceRule
  • Spring提供的默认RestClient是OverrideRestClient
  • Spring提供的默认检查存活组件是DummyPing
  • Spring定时更新服务列表的组件是PollingServerListUpdater

Comments