2、spring-cloud-commons 源码分析

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

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<Class<?>> abstractFeaturesList<NamedFeature> namedFeatures 这两个属性。

@Endpoint(id = "features")
public class FeaturesEndpoint implements ApplicationContextAware {

	private final List<HasFeatures> hasFeaturesList;

	private ApplicationContext context;

	public FeaturesEndpoint(List<HasFeatures> hasFeaturesList) {
		this.hasFeaturesList = hasFeaturesList;
	}
	
	@ReadOperation
	public Features features() {
		Features features = new Features();

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

			List<NamedFeature> 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<Feature> enabled = new ArrayList<>();

		final List<String> disabled = new ArrayList<>();

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

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

	}
}	

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

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

@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

{
	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:

@Import(EnableCircuitBreakerImportSelector.class)
public @interface EnableCircuitBreaker {
	...
}

selector也很简单,代码如下:

@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注解。

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

public interface DiscoveryClient {
	String description(); // 描述
	
	List<ServiceInstance> getInstances(String serviceId); // 根据服务id获取具体的服务实例
	
	List<String> 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子包内部还有其它一些功能:

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

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

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

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

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

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

4、serviceregistry功能

4.1、ServiceRegistry服务注册功能

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

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

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

@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实现类都会提供一个对应的服务注册实现

4.2、ServiceRegistry Auto-Registration

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

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

# 通过注解方式禁用自动注册服务
@EnableDiscoveryClient(autoRegister=false)

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

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

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

4.2.2、原理剖析

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

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

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

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

// 服务注册信息的配置数据
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注解,像下面的例子:

@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、客户端负载均衡功能

一些接口的定义:

public interface ServiceInstanceChooser {
	// 根据服务id获取具体的服务实例
    ServiceInstance choose(String serviceId);
}
public interface LoadBalancerClient extends ServiceInstanceChooser {
	// 根据serviceId使用ServiceInstance执行请求
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
	// 重载方法。同上
	<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
	// 基于服务实例和URI重新构造一个新的带有host和port信息的URI。比如http://myservice/path/to/service.这个地址 myservice 这个服务名将会替换成比如 192.168.1.122:8080
	URI reconstructURI(ServiceInstance instance, URI original);
}
public interface RestTemplateCustomizer {
	void customize(RestTemplate restTemplate);
}
@Order(LoadBalancerRequestTransformer.DEFAULT_ORDER)
public interface LoadBalancerRequestTransformer {
	public static final int DEFAULT_ORDER = 0;
	HttpRequest transformRequest(HttpRequest request, ServiceInstance instance);
}
public interface LoadBalancerRequest<T> {
	public T apply(ServiceInstance instance) throws Exception;
}

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

// 使用RestTemplateCustomizer定制化这些被@LoadBalanced注解修饰的RestTemplate
@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);
            }
        }
    });
}

// 内部的一个配置类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<ClientHttpRequestInterceptor> 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:

@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以及注入的方式?看下面的列子:

@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/

其它注解、接口、类

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

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

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

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

自动化配置类。在该模块的 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是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看