前面的章节我们讲了微服务专题06-云原生应用(Cloud Native Applications)。本节,继续微服务专题的内容分享,共计16小节,分别是:
本节内容重点为:
/env
端点的使用场景,并且解读其源码,了解其中奥秘@EnableConfigServer
、Environment
仓储@RefreshScope
基本用法和使用场景,并且说明其中的局限性/health
)以及 健康指标(Health Indicator)做过 SpringCloud 配置管理的同学一定会接触一些企业级的配置管理框架,这里给出参考。
百度 Disconf
携程 Apollo
阿里 Nacos
Spring Cloud Config
Netfix Archaius
Apache Zookeeper
传统的配置管理是基于 Spring Stack 来实现的,所以 Client 与 Server 是通过 Spring 进行关联的:
Q:Spring Cloud 的分布式配置如何设计的呢?
A:Spring Cloud 的配置读取是在客户端启动时就加载配置服务器。而通常分布在不同地域,不同机器上的客户端配置是不一样的,比如中国的 Client 的 QPS 是1000,而美国的 Client 的 QPS 是 500,则可通过服务熔断的机制 ${app.qps} 去设计。并且 SpringCloud 最新版本服务器端支持加载 Github/SVN、数据库以及配置文件这几种方式。
在传统的项目配置管理,Java Client 自行读取HttpClient,通常的流程是这样的:
Q:传统意义上的配置客户端通过Http 拉取配置服务器上的配置有什么弊端?
A: 通过http拉取的过程中,Http1.1 版本的协议是无状态的,即短连接。这就意味着,客户端每次在更新的时候就必须采取轮询策略,而长期运作的情况显然不是我们愿意看到的结果。
面对 JavaClient 采用 Http 短连接的弊病,我们通常可以采用第三方库来对配置文件进行管理。
开源项目 | 配置源顺序(配置优先级覆盖) | 配置源(存储媒介) | 转换类型(编程便利性) |
---|---|---|---|
Apache Commons Configuration | Configuration | 丰富,基本支持所有类型 | |
Spring FrameWork | addFirst()优先覆盖 | PropertySource |
pom坐标如下:
<dependency>
<groupId>commons-configurationgroupId>
<artifactId>commons-configurationartifactId>
<version>1.9version>
dependency>
展开源码,发现 commons-configuration 包里面的核心类 Configuration
提供大多数常见类型的 Value 转换。
原因在于 Properties 集成了Hashtable 的 key 和 value 都是 Object 类型:
接下来看一下 Configuration 的实现类:
实现类有很多种,这里举例几个常见的子类实现用以说明:
PropertiesConfiguration
: 将 Properties 作为 Configuration
配置
MapConfiguration
: 以 Map 形式存储 配置信息
EnvironmentConfiguration
: OS 环境变量SystemConfiguration
: Java 系统属性CompositeConfiguration
:将配置组合起来,存储方式为List < Configuration >
不论Spring原生的配置读取,还是第三方库的配置读取,最核心概念在于:配置源、以及它们优先次序、配置转换能力!
Q:看到这里,我们不禁思考,HTTP 资源算不算一个配置?
A:通常我们所说的配置源指的是文件、HTTP 资源、数据源、 Git 等,但是殊途同归,都是以URL形式存在,所以HTTP 资源算一个配置。
前面提到的 commons-configuration 方式使通过 Apache Commons Configuration 来实现的,那么在Spring FrameWork 则通过Environment作为媒介进行配置管理。
在其子实现类 ConfigurableEnvironment 有这样的方法:
MutablePropertySources getPropertySources();
而MutablePropertySources 则是用来存放配置源的集合的:
关于 PropertySource
配置源 ,对比 Apache 的 PropertiesConfiguration ,同样在 PropertySource 存在诸多实现类:
MapPropertySource
PropertiesPropertySource
CompositePropertySource
: 将配置组合起来,存储方式为LinkedHashSet
,特点就是有序并且可以去重。Tips: LinkedHashSet 与 LinkedHashMap 有什么区别呢?
SystemEnvironmentPropertySource
环境变量配置Spring Cloud 客户端配置定位扩展 : PropertySourceLocator
现在我们整理一下客户端与服务端的配置流程:
实现的效果是,通过配置 Spring Cloud Config 的服务端,将 Git 仓库中的配置加载出来:
我这里将git版本库放在了/resources/configs目录下,并用以不同的profile加以区分。
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.1.RELEASEversion>
<relativePath/>
parent>
<properties>
<spring-cloud.version>Hoxton.SR6spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starterartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
@EnableConfigServer
声明当前的服务是 Spring Cloud Config 的服务端。@SpringBootApplication
@EnableConfigServer
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
版本化配置
## 配置服务器应用名字
spring.application.name = config-server
## 设置服务端口号
server.port = 10086
## 配置服务器git本地文件系统路径
spring.cloud.config.server.git.uri = \
${user.dir}/src/main/resources/configs/
版本文件
config.properties文件:
name = jack
config-test.properties文件:
name = tom
当我们访问:http://localhost:10086/config/default 实际上读取的是configs目录下的config.properties配置文件。
当我们访问:http://localhost:10086/config/test 实际上读取的是configs目录下的config-test.properties配置文件。
Spring Cloud Config 实现一套完整的配置管理 API 设计。在配置的使用常采用三段式风格设置路径,即 /应用名/profile/ $ {label},$ {label} 代表分支。如果不声明分支则默认加载master主分支。如果profile环境也不声明就等同于 /应用名.properties。
以上演示的DEMO我们也要知道是有很多问题的:
前文 demo 中所演示的 Config Server 中涉及到 @EnableConfigServer
这个注解,现在我们就分析一下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {
}
注意使用了 @Import 说明实际配置类为 ConfigServerConfiguration
:
@Configuration
public class ConfigServerConfiguration {
class Marker {}
@Bean
public Marker enableConfigServerMarker() {
return new Marker();
}
}
我们发现,在 ConfigServerAutoConfiguration 里实际应用了 ConfigServerConfiguration 这个类:
@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class })
public class ConfigServerAutoConfiguration {
}
从这里我们发现:当应用配置类标注了
@EnableConfigSever
, 导入ConfigServerConfiguration
,并注册Marker
Bean,而Marker
Bean 也是作为ConfigServerAutoConfiguration
条件之一。
JdbcTemplate Bean 来源
JdbcTemplateAutoConfiguration
SQL 来源
JdbcEnvironmentProperties
,内容为:spring.cloud.config.server.jdbc.sql
,如果不配置,默认使用DEFAULT_SQL
,即: SELECT KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?
回顾上面的demo,结合源码,我们也会得出以下结论:
KEY | VALUE | APPLICATION | PROFILE | LABEL |
---|---|---|---|---|
name | zhangsan | config | default | master |
name | lisi | config | test | master |
本质说明:
JDBC :连接技术
DB : 存储介质
核心接口: EnvironmentRepository
Q:是否可以自定义 EnvironmentRepository
实现?
A:前提:如何激活自定义的 EnvironmentRepository
实现,首先找到为什么默认是 Git 作为配置仓库的原因:
@Configuration
@ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {
...
@Bean
public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
return gitEnvironmentRepositoryFactory.build(environmentProperties);
}
}
当 Spring 应用上下文没有出现 EnvironmentRepository
Bean 的时候,那么,默认激活 DefaultRepositoryConfiguration
(Git 实现),否则采用自定义实现。
自定义 EnvironmentRepository
Bean
@Bean
public EnvironmentRepository environmentRepository() {
return (String application, String profile, String label) -> {
Environment environment = new Environment("default", profile);
List<PropertySource> propertySources = environment.getPropertySources();
Map<String, Object> source = new HashMap<>();
source.put("name", "test");
PropertySource propertySource = new PropertySource("map", source);
// 追加 PropertySource
propertySources.add(propertySource);
return environment;
};
}
以上实现将失效
DefaultRepositoryConfiguration
装配。
Git 方式:早放弃
JDBC 方式:太简单
Zookeeper 方式: 比较适合做分布式配置
自定义方式:是高端玩家
本节代码地址:https://github.com/harrypottry/spring-cloud-config-server
更多架构知识,欢迎关注本套Java系列文章:Java架构师成长之路