微服务专题07 - Spring Cloud 配置管理

目录导航

    • 前言
    • 开源项目
      • 国内知名开源项目
      • 国外知名开源项目
    • 客户端
      • Spring Cloud Config 的前世今生
      • Java Client 自行读取 HttpClient
      • 配置三方库
        • Apache Commons Configuration
        • Spring FrameWork
      • Spring Cloud 客户端配置管理
    • 服务端
      • 基于 Git 实现
      • 设计原理
        • 案例分析 JDBC 实现
      • 自定义实现
      • 比较 Spring Cloud 内建配置仓储的实现
    • 后记

前言

前面的章节我们讲了微服务专题06-云原生应用(Cloud Native Applications)。本节,继续微服务专题的内容分享,共计16小节,分别是:

  • 微服务专题01-Spring Application
  • 微服务专题02-Spring Web MVC 视图技术
  • 微服务专题03-REST
  • 微服务专题04-Spring WebFlux 原理
  • 微服务专题05-Spring WebFlux 运用
  • 微服务专题06-云原生应用(Cloud Native Applications)
  • 微服务专题07-Spring Cloud 配置管理
  • 微服务专题08-Spring Cloud 服务发现
  • 微服务专题09-Spring Cloud 负载均衡
  • 微服务专题10-Spring Cloud 服务熔断
  • 微服务专题11-Spring Cloud 服务调用
  • 微服务专题12-Spring Cloud Gateway
  • 微服务专题13-Spring Cloud Stream (上)
  • 微服务专题14-Spring Cloud Bus
  • 微服务专题15-Spring Cloud Stream 实现
  • 微服务专题16-Spring Cloud 整体回顾

本节内容重点为:

  • Environment 端点:介绍/env 端点的使用场景,并且解读其源码,了解其中奥秘
  • 基本使用:介绍@EnableConfigServerEnvironment 仓储
  • 分布式配置官方实现:介绍 Spring 官方标准分布式配置实现方式:JDBC 实现
  • 动态配置属性 Bean:介绍@RefreshScope基本用法和使用场景,并且说明其中的局限性
  • 健康指标:介绍 Spring Boot 标准端口(/health)以及 健康指标(Health Indicator)
  • 健康指标自定义实现:实现分布式配置的健康指标自定义实现

开源项目

做过 SpringCloud 配置管理的同学一定会接触一些企业级的配置管理框架,这里给出参考。

国内知名开源项目

百度 Disconf

携程 Apollo

阿里 Nacos

国外知名开源项目

Spring Cloud Config

Netfix Archaius

Apache Zookeeper

客户端

Spring Cloud Config 的前世今生

传统的配置管理是基于 Spring Stack 来实现的,所以 Client 与 Server 是通过 Spring 进行关联的:
微服务专题07 - Spring Cloud 配置管理_第1张图片
Q:Spring Cloud 的分布式配置如何设计的呢?

A:Spring Cloud 的配置读取是在客户端启动时就加载配置服务器。而通常分布在不同地域,不同机器上的客户端配置是不一样的,比如中国的 Client 的 QPS 是1000,而美国的 Client 的 QPS 是 500,则可通过服务熔断的机制 ${app.qps} 去设计。并且 SpringCloud 最新版本服务器端支持加载 Github/SVN、数据库以及配置文件这几种方式。
微服务专题07 - Spring Cloud 配置管理_第2张图片

Java Client 自行读取 HttpClient

在传统的项目配置管理,Java Client 自行读取HttpClient,通常的流程是这样的:

  1. 首先获取配置文件文本信息
  2. 将文本信息转化为 Properties 对象
  3. 根据配置文件(yml/properties)的 key 读取到相应的值
  4. 业务逻辑代码加载 value 值(通过 Spring 注入配置)
  5. 新的配置就对于 APP 生效了

Q:传统意义上的配置客户端通过Http 拉取配置服务器上的配置有什么弊端?

A: 通过http拉取的过程中,Http1.1 版本的协议是无状态的,即短连接。这就意味着,客户端每次在更新的时候就必须采取轮询策略,而长期运作的情况显然不是我们愿意看到的结果。

配置三方库

面对 JavaClient 采用 Http 短连接的弊病,我们通常可以采用第三方库来对配置文件进行管理。

开源项目 配置源顺序(配置优先级覆盖) 配置源(存储媒介) 转换类型(编程便利性)
Apache Commons Configuration Configuration 丰富,基本支持所有类型
Spring FrameWork addFirst()优先覆盖 PropertySource

Apache Commons Configuration

pom坐标如下:

<dependency>
	<groupId>commons-configurationgroupId>
	<artifactId>commons-configurationartifactId>
	<version>1.9version>
dependency>

展开源码,发现 commons-configuration 包里面的核心类 Configuration 提供大多数常见类型的 Value 转换。
微服务专题07 - Spring Cloud 配置管理_第3张图片

原因在于 Properties 集成了Hashtable 的 key 和 value 都是 Object 类型:
微服务专题07 - Spring Cloud 配置管理_第4张图片
接下来看一下 Configuration 的实现类:
微服务专题07 - Spring Cloud 配置管理_第5张图片
实现类有很多种,这里举例几个常见的子类实现用以说明:

  • PropertiesConfiguration: 将 Properties 作为 Configuration 配置

  • MapConfiguration: 以 Map 形式存储 配置信息

    • EnvironmentConfiguration : OS 环境变量
    • SystemConfiguration : Java 系统属性
  • CompositeConfiguration:将配置组合起来,存储方式为List < Configuration >

不论Spring原生的配置读取,还是第三方库的配置读取,最核心概念在于:配置源、以及它们优先次序配置转换能力

Q:看到这里,我们不禁思考,HTTP 资源算不算一个配置?

A:通常我们所说的配置源指的是文件、HTTP 资源、数据源、 Git 等,但是殊途同归,都是以URL形式存在,所以HTTP 资源算一个配置。

  • 文件对应的URL路径 file:///
  • http资源对应的URL路径 http://
  • 数据源对应的URL路径jdbc://
  • git 对应的URL路径 git://

Spring FrameWork

前面提到的 commons-configuration 方式使通过 Apache Commons Configuration 来实现的,那么在Spring FrameWork 则通过Environment作为媒介进行配置管理。

微服务专题07 - Spring Cloud 配置管理_第6张图片

在其子实现类 ConfigurableEnvironment 有这样的方法:

MutablePropertySources getPropertySources();

而MutablePropertySources 则是用来存放配置源的集合的:
微服务专题07 - Spring Cloud 配置管理_第7张图片

关于 PropertySource 配置源 ,对比 Apache 的 PropertiesConfiguration ,同样在 PropertySource 存在诸多实现类:

  • MapPropertySource
    • PropertiesPropertySource
  • CompositePropertySource : 将配置组合起来,存储方式为LinkedHashSet ,特点就是有序并且可以去重。
    在这里插入图片描述

Tips: LinkedHashSet 与 LinkedHashMap 有什么区别呢?

  • SystemEnvironmentPropertySource 环境变量配置

Spring Cloud 客户端配置定位扩展 : PropertySourceLocator

Spring Cloud 客户端配置管理

现在我们整理一下客户端与服务端的配置流程:

微服务专题07 - Spring Cloud 配置管理_第8张图片

  1. 首先要明确一点,项目的外部化配置是有很多种形式的,比如命令行参数、Java System 属性、application properties 配置文件等。
  2. 但是现在想要通过一个服务器作为配置管理,就应该将配置中心的加载顺序放到首位。我们知道在 getProperties 的过程中,会采取 addFirst 的形式。
  3. 通过上一节的介绍了上下文层次的加载顺序问题,不就是正好可以解决这一个问题么?Bootstrap上下文是所有上下文的parent ,所以我们可以将配置中心置于Bootstrap ApplicationContext(Bootstrap父上下文)。将其他形式的配置设置为 Service ApplicationContext(服务级上下文)。
  4. 这样通过客户端启动时加载时就会优先加载来自于 Bootstrap 父上下文中的配置中心的内容。

服务端

基于 Git 实现

实现的效果是,通过配置 Spring Cloud Config 的服务端,将 Git 仓库中的配置加载出来:

我这里将git版本库放在了/resources/configs目录下,并用以不同的profile加以区分。

微服务专题07 - Spring Cloud 配置管理_第9张图片

  1. 配置服务端依赖,篇幅有限,完整依赖请移步至文末 Github 代码地址。
    <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>
  1. 通过 @EnableConfigServer 声明当前的服务是 Spring Cloud Config 的服务端。
@SpringBootApplication
@EnableConfigServer
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
  1. 配置文件相关

版本化配置

## 配置服务器应用名字
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
  1. 服务访问

当我们访问:http://localhost:10086/config/default 实际上读取的是configs目录下的config.properties配置文件。

微服务专题07 - Spring Cloud 配置管理_第10张图片

当我们访问:http://localhost:10086/config/test 实际上读取的是configs目录下的config-test.properties配置文件。

微服务专题07 - Spring Cloud 配置管理_第11张图片
注意git的版本号:

微服务专题07 - Spring Cloud 配置管理_第12张图片

Spring Cloud Config 实现一套完整的配置管理 API 设计。在配置的使用常采用三段式风格设置路径,即 /应用名/profile/ $ {label},$ {label} 代表分支。如果不声明分支则默认加载master主分支。如果profile环境也不声明就等同于 /应用名.properties。

以上演示的DEMO我们也要知道是有很多问题的:

  • 复杂的版本更新机制( Git 仓库)
    • 版本
    • 分支
    • 提交
    • 配置
  • 憋脚的内容更新(实时性不高)
    • 客户端第一次启动拉取
    • 需要整合 BUS 做更新通知

设计原理

前文 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 条件之一。

案例分析 JDBC 实现

  • 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 装配。

比较 Spring Cloud 内建配置仓储的实现

  • Git 方式:早放弃

  • JDBC 方式:太简单

  • Zookeeper 方式: 比较适合做分布式配置

  • 自定义方式:是高端玩家

后记

本节代码地址:https://github.com/harrypottry/spring-cloud-config-server

更多架构知识,欢迎关注本套Java系列文章:Java架构师成长之路

你可能感兴趣的:(微服务专题)