2.2 Spring Cloud特性
云原生应用程序开发风格鼓励在持续交付和价值驱动开发上采取最佳的实践策略,Spring Cloud提供了多种方式来促进云原生开发风格。Spring Cloud提供了一系列组件,可以在分布式系统中直接使用,这些组件大大降低了分布式系统的搭建和开发难度。
这些组件大多数由Spring Boot提供,Spring Cloud在此基础上添加了分布式系统的相关特性。Spring Cloud依赖于Spring Cloud Context和Spring Cloud Commons两个公共库,其中Spring Cloud Context为Spring Cloud应用程序上下文(ApplicationContext)提供了大量的实用工具和特性服务,而Spring Cloud Common是针对不同的Spring Cloud实现(如Spring Cloud Netflix Eureka和Spring Cloud Consul两种不同的服务注册与发现实现)提供上层抽象和公共类。
接下来将从这两个库着手,对Spring Cloud的相关特性进行简要介绍。
2.2.1 Spring Cloud Context:应用上下文
使用过Spring Boot的读者都或多或少地了解如何使用Spring构建应用,比如,配置一般都需要放置在统一的位置,暴露用于管理的网络接口等。Spring Cloud在这些最佳实践之上提供了新的特性,来适配微服务架构下的运维环境。
1. Bootstrap上下文
除了应用上下文配置(application.yml或者application.properties)之外,Spring Cloud应用程序还额外提供与Bootstrap上下文配置相关的应用属性。Bootstrap上下文对于主程序来说是一个父级上下文,它支持从外部资源中加载配置文件,和解密本地外部配置文件中的属性。Bootstrap上下文和应用上下文将共享一个环境(Environment),这是所有Spring应用程序的外部属性来源。一般来讲,Bootstrap上下文中的属性优先级较高,所以它们不能被本地配置所覆盖。
Bootstrap上下文使用与主程序不同的规则来加载外部配置。因此bootstrap.yml用于为Bootstrap上下文加载外部配置,区别于应用上下文的application.yml或者application. properties。一个简单的bootstrap.yml的例子如下所示:
spring: application: name: my-application cloud: config: uri: ${CONFIG_SERVER:http://localhost:8080}
如果想要禁止Bootstrap引导过程,可以在bootstrap.yml中设置,如下所示:
spring: cloud: bootstrap: enabled: false
2.应用上下文层级
Spring的上下文有一个特性:子级上下文将从父级中继承属性源和配置文件。如果通过SpringApplication或者SpringApplicationBuilder来构建应用程序上下文,那么Bootstrap上下文将会成为该应用程序上下文的父级上下文。
在Bootstrap上下文中扫描到的非空的PropertySourceLocators会以高优先级添加到CompositePropertySource中。如果通过bootstrap.yml来配置Bootstrap上下文,且在设定好父级上下文的情况下,bootstrap.yml中的属性会添加到子级的上下文。它们的优先级低于application.yml和其他添加到子级中作为创建Spring Boot应用的属性源。
基于属性源的排序规则,Bootstrap上下文中的属性优先,但是需要注意这些属性并不包含任何来自bootstrap.yml的数据。bootstrap.yml中的属性具备非常低的优先级,因此可以作为默认值。
可以简单地将父级上下文设置为应用上下文来扩展上下文的层次结构。Bootstrap上下文将会是最高级别上下文的父级。每一个在层次结构中的上下文都有它自己的Bootstrap属性源(可能为空),来避免无意中将父级上下文中的属性传递到它的后代中。层次结构中的每一个上下文原则上应该拥有自己不同的spring.application.name,以便在有配置中心的时候也能有不同的远程属性源。来自子级上下文的属性可以覆盖父级中的具有相同名称和属性源名称的属性。
3.修改Bootstrap配置文件的位置
bootstrap.yml的位置可以通过在配置属性中设置spring.cloud.bootstrap.name(默认是bootstrap)或者spring.cloud.bootstrap.location来修改。
4.重载远程属性
通过Bootstrap上下文添加到应用程序的属性源通常是远程的,例如来自配置中心,通常本地的配置文件不能覆盖这些远程属性源。一般来说过,启动命令行参数的优先级高于远程配置,可以通过设定启动命令行参数的方式覆盖远程配置。
如果想使用应用程序的系统属性或者配置文件覆盖远程属性,那么远程属性源必须设置为spring.cloud.config.allowOverride=true(这个配置在本地设置不会生效)。在远程属性源中设定上述配置后,就可以通过更为细粒度的设置来控制远程属性是否能被重载,具体配置如下所示。
spring: cloud: config: overrideNone: true #本地属性覆盖所有的远程属性源 overrideSystemProperties: false#仅覆盖远程属性源中的系统属性和环境变量
5.自定义Bootstrap配置
自定义Bootstrap配置过程与Spring Boot自动配置运行原理类似,具体操作是在/META-INF/spring.factories文件中添加org.springframework.cloud.bootstrap.BootstrapConfiguration配置项。配置项的值是一系列用来创建Context的@Configuration配置类,配置类之间以逗号分隔。这些类可以为应用上下文提供Bean实例,配置类可以通过标记@Order来控制Bean实例初始化序列。如下例所示,在引导过程中添加了一个LogAutoConfiguration的配置类,为应用程序添加日志相关的Bean实例:
org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.demo.starter.config.LogAutoConfiguration
6.自定义Bootstrap属性源
默认的Bootstrap外部配置属性源是Spring Cloud Config Server,即使用配置中心加载外部属性。但是用户也可以通过将PropertySourceLocator类型的Bean实例添加到Bootstrap上下文(在spring.factories添加对应的配置类)来添加额外的属性来源。通过这种方法可以从不同的服务器或者数据库中加载额外的属性,如下所示:
@Configuration public class CustomPropertySourceLocator implements PropertySourceLocator { @Override public PropertySource<? > locate(Environment environment){ return new MapPropertySource("customProperty", Collections.<String, Object>singletonMap("property.from.sample.custom. source", "worked as intended")); } }
上述代码中传入的Environment参数用于创建应用上下文,它具有Spring Boot提供的属性源,可以使用它们来加载特定的属性源(例如重新设置spring.application.name)。可以在META-INF/spring.factories文件中添加如下记录来配置属性源:
org.springframework.cloud.bootstrap.BootstrapConfiguration=\ sample.custom.CustomPropertySourceLocator
上述配置令应用程序可以使用CustomPropertySourceLocator作为其属性源。
7. Environment变化
Config Client应用程序会监听EnvironmentChangeEvent事件,当监听到一个EnvironmentChangeEvent时,它将持有一个被改变的键值对列表,应用程序使用这些值来:□ 重新绑定所有的@ConfigurationProperties的Bean实例,更新本地的配置属性。
□ 为在logging.level.*的所有属性设置日志的等级。
一般来讲,Config Client默认不会使用轮询方法来监听Environment中的改变。在Spring Cloud中,Spring Cloud Config Server使用Spring Cloud Bus将EnvironmentChangeEvent广播到所有的Config Client中,通知它们Environment发生变化。
EnvironmentChangeEvent是一个事件类,用于在Environment发生修改时发布事件。开发者可以通过访问/configprops端点(常规的Spring Boot Actuator端点)来验证这些更改是否绑定到@ConfigurationProperties的Bean实例上。例如一个DataSource的最大连接数量在运行时被改变了(DataSource默认由Spring Boot创建,属于@ConfigurationProperties的Bean)并且动态增加容量,可以通过查看Config Client应用程序的/configprops端点来验证DataSource的最大连接池数量是否发生变化。
8.刷新范围
一个被标记为@RefreshScope的Spring Bean实例在配置发生变更时可以重新进行初始化,即动态刷新配置,这是为了解决状态Bean实例只能在初始化的时候才能进行属性注入的问题。
被@RefreshScope修饰的Bean实例是懒加载的,即当它们被使用的时候才会进行初始化(方法被调用的时候),想要在下次方法调用前强制重新初始化一个Bean实例,只需要将它的缓存失效即可。
RefreshScope是上下文中的一个Bean实例,它有一个公共方法refreshAll,该方法可以通过清除目标缓存来刷新作用域中的所有Bean实例。RefreshScope也有一个refresh方法来按照名字刷新单个Bean。
2.2.2 Spring Cloud Commons:公共抽象
Spring Cloud将服务发现、负载均衡和断路器等通用模型封装在一个公共抽象中,可以被所有的Spring Cloud客户端使用,不依赖于具体的实现(例如服务发现就有Eureka和Consul等不同的实现),这些公共抽象位于Spring Cloud Commons项目中。
1. @EnableDiscoveryClient注解
@EnableDiscoveryClient注解用于在META-INF/spring.factories文件中查找DiscoveryClient(DiscoveryClient为服务发现功能抽象类)的实现。spring.factories文件的org.springframework. cloud.client.discovery.EnableDiscoveryClient配置项可以指定DiscoveryClient的实现类。DiscoveryClient目前的实现有Spring Cloud Netflix Eureka、Spring Cloud Consul Discovery和Spring Cloud Zookeeper Discovery。
DiscoveryClient的实现类会自动将本地的Spring Boot服务注册到远程服务发现中心。可以通过在@EnableDiscoveryClient中设置autoRegister=false来禁止自动注册行为。
在Finchley版本的Spring Cloud中,不需要显式使用@EnableDiscoveryClient来开启客户端的服务注册与发现功能。只要在类路径中,有DiscoveryClient的实现就能使Spring Cloud应用注册到服务发现中心。
2.服务注册(ServiceRegistry)
Spring Cloud Commons的ServiceRegister接口提供register(服务注册)和deregister(服务下线)方法,使开发者可以自定义注册服务的逻辑,如下所示:
public interface ServiceRegistry<R extends Registration> { void register(R registration); void deregister(R registration); }
每一个ServiceRegistry的实现都拥有自己的注册表实现,如Eureka、Consul等。
3. RestTemplate的负载均衡
创建RestTemplate实例的时候,使用@LoadBalanced注解可以将RestTemplate自动配置为使用负载均衡的状态。@LoadBalanced将使用Ribbon为RestTemplate执行负载均衡策略。
创建负载均衡的RestTemplate不再能通过自动配置来创建,必须通过配置类创建,具体实例如下所示:
@Configuration public class MyConfiguration { @LoadBalanced @Bean RestTemplate restTemplate(){ return new RestTemplate(); } } public class MyApplication { @Autowired private RestTemplate restTemplate; public String getMyApplicationName(){ //使用restTemplate访问my-application微服务的/name接口 String name = restTemplate.getForObject("http://my-application/name", String.class); return name; } }
URI需要使用服务名来指定需要访问应用服务,Ribbon客户端将通过服务名从服务发现应用处获取具体的服务地址来创建一个完整的网络地址,以实现网络调用。
4. RestTemplate的失败重试
负载均衡的RestTemplate可以添加失败重试机制。默认情况下,失败重试机制是关闭的,启用方式是将Spring Retry添加到应用程序的类路径中。还可以设置spring.cloud. loadbalancer.retry.enabled=false禁止类路径中Spring retry的重试逻辑。
如果想要添加一个或者多个RetryListener到重试请求中,可以创建一个类型为LoadBalancedRetryListenerFactory的Bean,用来返回将要用于重试机制的RetryListener的列表,如下代码所示:
@Configuration public class RryListenerConfiguration { @Bean LoadBalancedRetryListenerFactory retryListenerFactory(){ return new LoadBalancedRetryListenerFactory(){ @Override public RetryListener[] createRetryListeners(String service){ return new RetryListener[]{new RetryListener(){ @Override // 重试开始前的工作 public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback){ return true; } // 重试结束后的工作 @Override public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable){ } // 重试出错后的工作 @Override public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable){ } }}; } }; } }
其中,自定义配置类中定义了生成LoadBalancedRetryListenerFactory实例的@Bean方法,该工厂类的createRetryListeners方法会生成一个RetryListener实例,用于进行网络请求的重试。