avatar

目录
spring-cloud-commons 源码分析

SpringCloud组件内部一定会有spring-cloud-commonsspring-cloud-context 这两个依赖中的一个。比如 spring-cloud-netflix-eureka-server, spring-cloud-netflix-eureka-client, spring-cloud-netflix-ribbon。它们内部的这两个依赖都是optional。这些组件对应的starter内部使用了spring-cloud-starter 依赖,因为spring-cloud-starter依赖内部依赖了spring-cloud-contextspring-cloud-commonsspring-boot-starter(springboot全套架构)。

本文将分析spring-cloud-commons模块。关于spring-cloud-context将在下一篇文章中分析。

spring-cloud-commons模块是spring在分布式领域上(服务发现,服务注册,断路器,负载均衡)的规范定义(spring-cloud-netflix是具体的实现,也就是Netflix OSS里的各种组件实现了这个commons规范),可以被所有的Spring Cloud客户端使用(比如服务发现领域的eurekaconsul)。下面将根据包名来分析一下内部的一些接口和类。

1、actuator功能

actuator子包里提供了一个idfeaturesFeaturesEndpoint。该Endpoint里会展示应用具体的feature。具体的内容在HasFeatures集合属性中,HasFeatures内部包含 List> abstractFeaturesList namedFeatures 这两个属性。

java
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@Endpoint(id = "features")
public class FeaturesEndpoint implements ApplicationContextAware {

private final List hasFeaturesList;

private ApplicationContext context;

public FeaturesEndpoint(List hasFeaturesList) {
this.hasFeaturesList = hasFeaturesList;
}

@ReadOperation
public Features features() {
Features features = new Features();

for (HasFeatures hasFeatures : this.hasFeaturesList) {
List> abstractFeatures = hasFeatures.getAbstractFeatures();
if (abstractFeatures != null) {
for (Class clazz : abstractFeatures) {
addAbstractFeature(features, clazz);
}
}

List namedFeatures = hasFeatures.getNamedFeatures();
if (namedFeatures != null) {
for (NamedFeature namedFeature : namedFeatures) {
addFeature(features, namedFeature);
}
}
}

return features;
}

private void addAbstractFeature(Features features, Class type) {
String featureName = type.getSimpleName();
try {
Object bean = this.context.getBean(type);
Class beanClass = bean.getClass();
addFeature(features, new NamedFeature(featureName, beanClass));
}
catch (NoSuchBeanDefinitionException e) {
features.getDisabled().add(featureName);
}
}

private void addFeature(Features features, NamedFeature feature) {
Class type = feature.getType();
features.getEnabled()
.add(new Feature(feature.getName(), type.getCanonicalName(),
type.getPackage().getImplementationVersion(),
type.getPackage().getImplementationVendor()));
}

static class Features {

final List enabled = new ArrayList<>();

final List disabled = new ArrayList<>();

public List getEnabled() {
return this.enabled;
}

public List getDisabled() {
return this.disabled;
}

}
}

NamedFeature里有 String nameClass type 这两个属性。

  • abstractFeatures属性处理过程:遍历abstractFeatures集合,在ApplicationContext中找出具体Class的bean。然后根据这个具体的Class构造Feature(Feature拥有type,name,version和vendor属性)。

  • namedFeatures属性处理过程:遍历namedFeatures集合,直接根据NamedFeature李的name和type构造Feature。

比如CommonsClientAutoConfiguration里就使用如下方式构造了一个HasFeatures。这个HasFeatures只有abstractFeatures属性有值,对应的Class是DiscoveryClient和LoadBalancerClient:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration(proxyBeanMethods = false)
public class CommonsClientAutoConfiguration {
... 省略无关代码

protected static class DiscoveryLoadBalancerConfiguration {
... 省略无关代码

@Bean
public HasFeatures commonsFeatures() {
return HasFeatures.abstractFeatures(DiscoveryClient.class,
LoadBalancerClient.class);
}
}
}

下面就是一个FeaturesEndpoint内容,对应DiscoveryClientLoadBalancerClient接口,具体的type就是CompositeDiscoveryClientRibbonLoadBalancerClient

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
27
28
29
30
31
32
33
34
{
enabled: [{
type: "org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient",
name: "DiscoveryClient",
version: "1.3.3.RELEASE",
vendor: "Pivotal Software, Inc."
},
{
type: "org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient",
name: "LoadBalancerClient",
version: "1.4.4.RELEASE",
vendor: "Pivotal Software, Inc."
},
{
type: "com.netflix.ribbon.Ribbon",
name: "Ribbon",
version: "2.2.5",
vendor: null
},
{
type: "rx.Observable",
name: "MVC Observable",
version: "1.2.0",
vendor: null
},
{
type: "rx.Single",
name: "MVC Single",
version: "1.2.0",
vendor: null
}
],
disabled: []
}

2、circuitbreaker功能

断路器功能。

circuitbreaker子包里面定义了一个注解@EnableCircuitBreaker和一个Import Selector。只要使用了该注解就会import这个selector:

Code
1
2
3
4
@Import(EnableCircuitBreakerImportSelector.class)
public @interface EnableCircuitBreaker {
...
}

selector也很简单,代码如下:

java
1
2
3
4
5
6
7
8
9
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableCircuitBreakerImportSelector extends
SpringFactoryImportSelector<EnableCircuitBreaker> {
@Override
protected boolean isEnabled() {
return getEnvironment().getProperty(
"spring.cloud.circuit.breaker.enabled", Boolean.class, Boolean.TRUE);
}
}

继承了SpringFactoryImportSelector, 内部会使用工厂加载机制。去加载META-INF/spring.factories里key为 org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker的类。该机制生效的前期是 spring.cloud.circuit.breaker.enabled配置为true,默认值就是true。

circuitbreaker子包相当于了定义了断路器的加载机制。在spring.factories里配置对应的类和开关配置即可生效。具体的实现由其它模块提供。

3、discovery功能

3.1、服务发现功能

定义了DiscoveryClient接口和EnableDiscoveryClient注解。

定义了一些各种服务发现组件客户端里的读取服务操作:

java
1
2
3
4
5
6
7
public interface DiscoveryClient {
String description(); // 描述

List getInstances(String serviceId); // 根据服务id获取具体的服务实例

List getServices(); // 获取所有的服务id集合
}

@EnableDiscoveryClient注解import了EnableDiscoveryClientImportSelector这个selector。该注解内部有个属性 boolean autoRegister() default true;表示是否自动注册,默认是true。

selector内部会找出 META-INF/spring.factories里key为org.springframework.cloud.client.discovery.EnableDiscoveryClient的类。

如果自动注册属性为true,会在找出的这些类里再加上一个类:org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfigurationAutoServiceRegistrationConfiguration内部会使用@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)触发构造AutoServiceRegistrationProperties这个bean。像eureka,nacos,它们的自动化配置类里都使用了@ConditionalOnBean(AutoServiceRegistrationProperties.class)来确保存在AutoServiceRegistrationProperties这个bean存在的时候才会构造AutoServiceRegistration进行注册。

如果自动注册属性为false,在Environment里加一个PropertySource,内部的配置项是spring.cloud.service-registry.auto-registration.enabled,值是false(代表不构造AutoServiceRegistrationProperties.class)。这样eureka,nacos都不会注册

3.2、Health Indicator

springboot 中提供了一个健康检查的接口HealthIndicator, DiscoveryClient能够通过实现DiscoveryHealthIndicator来做健康检查。设置spring.cloud.discovery.client.composite-indicator.enabled=false来禁用这种混和的健康检查;DiscoveryClientHealthIndicator通常是自动配置的,设置spring.cloud.discovery.client.health-indicator.enabled=false来禁用;设置spring.cloud.discovery.client.health-indicator.include-description=false来禁用description字段,如果没有禁用,就会一直向上层传递。

3.3、Ordering DiscoveryClient instances

DiscoveryClient继承了Ordered; 当你使用多个服务发现的时候这个会很有用,可以定义通过这种方式来按照指定的顺序来从注册中心加载bean。DiscoveryClient默认的order设置的是 0 ;如果想要为你自己实现的DiscoveryClient设置不同的order,仅仅需要覆盖getOrder()。除此之外Spring Cloud还提供了配置spring.cloud.{clientIdentifier}.discovery.order来设置order,这其中主要的实现有ConsulDiscoveryClient, EurekaDiscoveryClient, ZookeeperDiscoveryClient

3.4、discovery子包内部还有其它一些功能:

  • simple

简单的服务发现实现类 SimpleDiscoveryClient,具体的服务实例从 SimpleDiscoveryProperties 配置中获取。 SimpleDiscoveryProperties 配置 读取前缀为 spring.cloud.discovery.client.simple 的配置。读取的结果放到Map里 Map>。这里 SimpleServiceInstance 实现了ServiceInstance接口。 具体的属性值从 SimpleDiscoveryProperties 中获取

SimpleDiscoveryClientAutoConfiguration 自动化配置类在 spring.factories里key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置项中。内部会构造 SimpleDiscoveryProperties、 SimpleDiscoveryClient

  • noop

什么都不做的服务发现实现类,已经被废弃,建议使用 simple 子模块里的类代替

  • health

SpringBoot的那套health机制与SpringCloud结合。使用DiscoveryClient获取服务实例的信息

  • event

定义了一些心跳检测事件,服务注册事件

  • composite

定义了 CompositeDiscoveryClient。 看名字也知道,组合各个服务发现客户端的一个客户端。默认会根据CompositeDiscoveryClientAutoConfiguration自动化配置类构造出CompositeDiscoveryClient。默认清下我们注入的DiscoveryClient就是这个CompositeDiscoveryClient

4、serviceregistry功能

4.1、ServiceRegistry服务注册功能

定义了服务注册的接口 ServiceRegistry,Registration接口继承了服务实例ServiceInstance接口,未新增新方法,留作以后扩展使用。

java
1
2
3
4
5
6
7
8
9
10
11
12
public interface ServiceRegistry<R extends Registration> {
// 注册服务实例
void register(R registration);
// 下线服务实例
void deregister(R registration);
// 生命周期方法,关闭操作
void close();
// 设置服务实例的状态,状态值由具体的实现类决定
void setStatus(R registration, String status);
// 获取服务实例的状态
T getStatus(R registration);
}

比如要取消自动注册,改为手动注册,示例代码如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
// 这里autoRegister属性设置为false表示取消自动注册
@EnableDiscoveryClient(autoRegister=false)
public class MyConfiguration {
// 服务注册接口,其实现是一个
private ServiceRegistry registry;

public MyConfiguration(ServiceRegistry registry) {
this.registry = registry;
}

// called through some external process, such as an event or a custom actuator endpoint
public void register() {
// 要注册的服务实例信息
Registration registration = constructRegistration();

// 由底层的服务注册实现去注册
this.registry.register(registration);
}
}

每个ServiceRegistry实现类都会提供一个对应的服务注册实现

  • ZookeeperRegistration 使用的 ZookeeperServiceRegistry
  • EurekaRegistration 使用的 EurekaServiceRegistry
  • ConsulRegistration 使用的 ConsulServiceRegistry

4.2、ServiceRegistry Auto-Registration

4.2.1、禁用自动注册服务功能

默认情况下ServiceRegistry的实现类在运行的时候会自动注册服务,两种方式来禁用自动注册服务

Code
1
2
3
4
5
# 通过注解方式禁用自动注册服务
@EnableDiscoveryClient(autoRegister=false)

# 通过配置yml属性配置方式禁用自动注册服务
spring.cloud.service-registry.auto-registration.enabled=false

当一个服务自动注册的时会触发两个事件:

  • 第一个是InstancePreRegisteredEvent,在注册之前触发;
  • 第二个是InstanceRegisteredEvent 在注册完成之后触发;可以使用ApplicationListener来监听这两个事件

spring.cloud.service-registry.auto-registration.enabled=false的时候就不会触发这两个事件

4.2.2、原理剖析

spring-cloud-commons项目的serviceresgistry包中定义了一些自动化配置类:

  • ServiceRegistryAutoConfiguration
    内部会根据条件注解判断是否构造ServiceRegistryEndpoint,该endpoint会暴露ServiceRegistry的状态信息,也可以设置ServiceRegistry的状态信息。

    Spring Cloud Commons 提供了一个/service-registry 端点,这个endpoint依赖于容器中的Registration。GET请求这个地址将会返回Registration的状态;POST请求这个地址可以修改Registration,这个json格式的body中必须要包含一个status;查询ServiceRegistry的实现类文档来确定status的值;比如Eureka的状态值:UP, DOWN, OUT_OF_SERVICE, UNKNOWN.

  • AutoServiceRegistrationAutoConfiguration

@EnableDiscoveryClient注解打开自注册开关的时候才会生效,内部import了AutoServiceRegistrationConfiguration这个类,该类内部会使用@EnableConfigurationProperties注解构造AutoServiceRegistrationProperties这个bean

  • 定义了一个接口AutoServiceRegistration 和一个抽象类AbstractAutoServiceRegistration,用于处理服务自动注册逻辑。一般我们自定义的服务注册逻辑只需要继承该类即可。

AutoServiceRegistration接口无任何方法声明,用于标记是否是服务自动注册。

AbstractAutoServiceRegistration 抽象类实现了AutoServiceRegistration接口,定义了4个抽象方法:

Code
1
2
3
4
5
6
7
8
// 服务注册信息的配置数据
protected abstract Object getConfiguration();
// 该注册器是否可用
protected abstract boolean isEnabled();
// 获取注册信息
protected abstract R getRegistration();
// 获取注册信息的management信息
protected abstract R getManagementRegistration();

AbstractAutoServiceRegistration 抽象类内部逻辑总结:

  1. 构造方法里必须有个ServiceRegistry参数,服务注册相关的逻辑都使用该接口完成

  2. 监听WebServerInitializedEvent事件。
    当WebServer初始化完毕后(Spring ApplicationContext也已经refresh后),使用ServiceRegistry注册服务,具体的服务信息在抽象方法getRegistration()里由子类实现。当子类实现的getManagementRegistration()接口有返回具体的注册信息并且配置的management信息后注册这个management信息

  3. 该类销毁的时候使用ServiceRegistry下线服务(下线过程跟注册过程雷同,下线getRegistration()
    getManagementRegistration()方法里返回的注册信息),并调用ServiceRegistry的close方法关闭注册器。

总结一下,在SpringCloud体系下要实现新的服务注册、发现需要这6个步骤(最新版本的spring-cloud-commons已经建议我们直接使用Registration,废弃ServiceInstance):

  1. 实现ServiceRegistry接口,完成服务注册自身的具体逻辑
  2. 实现Registration接口,完成服务注册过程中获取注册信息的操作
  3. 继承AbstractAutoServiceRegistration,完成服务注册前后的逻辑
  4. 实现DiscoveryClient接口,完成服务发现的具体逻辑
  5. 实现ServiceInstance接口,在DiscoveryClient接口中被使用,完成服务注册组件与SpringCloud注册信息的转换
    自动化配置类,将这些Bean进行构造

5、loadbalancer功能

5.1、使用示例

创建一个支持负载均衡的RestTemplate,使用@LoadBalanced@Bean注解,像下面的例子:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class MyConfiguration {

@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}

public class MyClass {
@Autowired
private RestTemplate restTemplate;

public String doOtherStuff() {
String results = restTemplate.getForObject("http://stores/stores", String.class);
return results;
}
}

5.2、客户端负载均衡功能

一些接口的定义:

  • ServiceInstanceChooser:服务实例选择器,使用load balancer根据serviceId获取具体的实例。
Code
1
2
3
4
public interface ServiceInstanceChooser {
// 根据服务id获取具体的服务实例
ServiceInstance choose(String serviceId);
}
  • LoadBalancerClient:负载均衡客户端,继承ServiceInstanceChooser
Code
1
2
3
4
5
6
7
8
public interface LoadBalancerClient extends ServiceInstanceChooser {
// 根据serviceId使用ServiceInstance执行请求
T execute(String serviceId, LoadBalancerRequest request) throws IOException;
// 重载方法。同上
T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException;
// 基于服务实例和URI重新构造一个新的带有host和port信息的URI。比如http://myservice/path/to/service.这个地址 myservice 这个服务名将会替换成比如 192.168.1.122:8080
URI reconstructURI(ServiceInstance instance, URI original);
}
  • RestTemplateCustomizer:RestTemplate的定制化器。
Code
1
2
3
public interface RestTemplateCustomizer {
void customize(RestTemplate restTemplate);
}
  • LoadBalancerRequestTransformer:HttpRequest转换器,根据ServiceInstance转换成一个新的具有load balance功能的HttpRequest。
Code
1
2
3
4
5
@Order(LoadBalancerRequestTransformer.DEFAULT_ORDER)
public interface LoadBalancerRequestTransformer {
public static final int DEFAULT_ORDER = 0;
HttpRequest transformRequest(HttpRequest request, ServiceInstance instance);
}
  • LoadBalancerRequest:函数式接口。对ServiceInstance操作并返回具体的泛型T。LoadBalancerRequestFactory的createRequest方法内部实现了该接口。实现过程中使用LoadBalancerRequestTransformer对request进行转换并返回了ClientHttpResponse。
Code
1
2
3
public interface LoadBalancerRequest {
public T apply(ServiceInstance instance) throws Exception;
}
  • @LoadBalanced注解用于修饰RestTemplate,表示使用负载均衡客户端。

使用该注解修饰的RestTemplate会在LoadBalancerAutoConfiguration自动化配置类中被处理:

Code
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 使用RestTemplateCustomizer定制化这些被@LoadBalanced注解修饰的RestTemplate
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}

// 内部的一个配置类LoadBalancerInterceptorConfig
// 如果没有依赖spring-retry模块。如果依赖spring-retry模块的话会构造另外一个配置类RetryInterceptorAutoConfiguration。
// 内部也会1个拦截器和1个定制化器,分别是RetryLoadBalancerInterceptor和RetryLoadBalancerInterceptor。原理类似

@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
// 构造http拦截器
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
// 构造定制化器RestTemplateCustomizer,为这些RestTemplate添加拦截器LoadBalancerInterceptor
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}

LoadBalancerInterceptor拦截器内部会对request请求进行拦截。拦截器内部使用LoadBalancerClient完成请求的调用,这里调用的时候需要的LoadBalancerRequestLoadBalancerRequestFactory构造,LoadBalancerRequestFactory内部使用LoadBalancerRequestTransformer对request进行转换。

5.3 Retrying Failed Requests

RestTemplate可以配置请求失败后的重试策略;默认这个逻辑是禁止的,如果需要可以开启,只需要添加 Spring Retry到classpath; 如果spring retry已经在classpath,你想要禁用这个retry的功能,那么可以配置spring.cloud.loadbalancer.retry.enabled=false

如果想要自定义一个BackOffPolicy,需要创建一个LoadBalancedRetryFactory并覆写方法createBackOffPolicy; eg:

Code
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class MyConfiguration {
@Bean
LoadBalancedRetryFactory retryFactory() {
return new LoadBalancedRetryFactory() {
@Override
public BackOffPolicy createBackOffPolicy(String service) {
return new ExponentialBackOffPolicy();
}
};
}
}

5.4 Multiple RestTemplate objects

如何创建一个支持负载均衡的RestTemplate和不支持负载均衡的RestTemplate以及注入的方式?看下面的列子:

Code
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
27
28
29
30
31
32
@Configuration
public class MyConfiguration {

@LoadBalanced
@Bean
RestTemplate loadBalanced() {
return new RestTemplate();
}

@Primary
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}

public class MyClass {
@Autowired
private RestTemplate restTemplate;

@Autowired
@LoadBalanced
private RestTemplate loadBalanced;

public String doOtherStuff() {
return loadBalanced.getForObject("http://stores/stores", String.class);
}

public String doStuff() {
return restTemplate.getForObject("http://example.com", String.class);
}
}

@Primary的作用是在使用@Autowired注入时,如果发现了多个类型的bean, 就选择使用了@Primary的bean

如果遇到了这个异常java.lang.IllegalArgumentException: Can not set org.springframework.web.client.RestTemplate field com.my.app.Foo.restTemplate to com.sun.proxy.$Proxy89,可以尝试注入类型修改RestOperations或者设置spring.aop.proxyTargetClass=true

6、hypermedia功能

springcloud对hateoas在服务发现领域上的支持。

关于hateoas可以参考一些资料:

https://github.com/spring-projects/spring-hateoas

https://spring.io/guides/gs/rest-hateoas/

其它注解、接口、类

  • @SpringCloudApplication注解

整合了@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker这3个注解,说明@SpringCloudApplication注解表示一个分布式应用注解

  • ServiceInstance接口

表示服务发现系统里的一个服务实例

  • DefaultServiceInstance

实现了ServiceInstance接口,是个默认的服务实例的实现

  • HostInfoEnvironmentPostProcessor

属于EnvironmentPostProcessor。这个postprocessor会在`Environment里加上当前vm的hostname和ip信息

  • CommonsClientAutoConfiguration

自动化配置类。在该模块的 META-INF/spring.factories里配置,key为org.springframework.boot.autoconfigure.EnableAutoConfiguration。所以默认会被加载,内部会构造一些HealthIndicator,一些Endpoint

Spring Cloud Alibaba内部的spring-cloud-alibaba-nacos-discovery模块实现了spring-cloud-commons规范,提供了基于Nacos的服务发现,服务注册功能。Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

文章作者: calebzhao
文章链接: https://calebzhao.github.io/2019/12/29/spring-cloud-commons-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 calebzhao的博客
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论