1、分布式配置中心

本文详细介绍了分布式配置中心的背景、需求、解决的问题,特别关注Spring Boot整合ConfigService的代码实现,以及自定义分布式配置中心的思考。讨论了Spring配置源的实现方式及其优先级,同时分析了ConfigService基于JDBC的源码,揭示了其工作原理。
摘要由CSDN通过智能技术生成

目录

1、分布式配置

1.1、分布式配置的开源框架

1.2、分布式配置需求的产生

1.3、分布式配置解决什么问题

1.4、springboot 整合 ConfigService  代码

1.5、分布式配置自定义实现

1.5.1、在某个服务中如何获取统一的配置?

1.5.2、统一配置的定义有那几种方式?

1.5.3、Spring 配置源的实现方式有哪些,优先级是怎样的?

1.6、ConfigService 基于 JDBC 的源码分析

1.7、原理本质说明


1、分布式配置

1.1、分布式配置的开源框架

国内的开源框架:百度 Disconf、携程 Appllo、阿里 Nacos、Consul;

国外的开源框架:Spring Cloud Config、Netflix Archaiux、Apache Zookeeper;

1.2、分布式配置需求的产生

在传统系统中,我们经常有系统参数这个功能模块,这个系统参数通常是整个系统中需要用到的某些参数配置,比如说:指定某个文件下载路径或者上传路径等,需要文件上传或者下载就会自动获取该参数进行处理。

这些参数通常写在 properties 中或者定义成常量的。如果定义成常量在系统中,那么意味着我们下载路径需要发生变化时,需要重启整个系统。在我们生产的环境下对于重启系统带来的后果有哪些呢?大家自己普及了。所以单体架构中需要改变这些参数配置而不用重启服务的解决方案有很多种,通常的做法就是采用存储在数据库,需要使用的时候到后台去查数据即可。

1.3、分布式配置解决什么问题

在分布式架构中,某个服务需要用到上诉中某个文件的下载路径,该怎么解决呢?

分布式配置中心就是解决我们在分布式架构中使用系统参数的中间件。其中实现方法有很多,可以通过文件存储、Git 远程存储、数据库存储等解决方案。然后再把系统参数通过提供接口的方式提供出来,最后就完成了分布式配置的解决方案。

1.4、springboot 整合 ConfigService  代码

下列是 spring boot 项目整合 ConfigService 的分布式配置中心的基本代码

//1、添加依赖包
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-config-server</artifactId>
</dependency> 

//2、在启动类上添加 @EnableConfigServer
 @EnableConfigServer 
 @EnableDiscoveryClient 
 @SpringBootConfiguration 
 @EnableAutoConfiguration 
 public class ConfigServerApp { 
     public static void main(String[] args) { 
         SpringApplication.run(ConfigServerApp.class, args); 
     } 
 }

//3、添加 application.yml 的配置
server:
  port: 4001
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git: #指向的是配置文件所在的git项目uri(下列两种形式)
          #定义一个本地文件,需要与 git 连接,也就是有 .git 文件
          #uri: ${user.dir}/springcloud-config/config-file 
          uri: https://github.com/xxx/SpringCloudDemo
          search-paths: {application}

//4、启动并验证 Spring Cloud Config Server 项目
http://localhost:4001/config/default/master/123.yml

通过页面访问路径验证是否可以获取 git 上面的配置信息

1.5、分布式配置自定义实现

通过理解分布式配置中心的原理,分析如果自己实现分布式配置中心需要哪些步骤,需要考虑哪些问题?如何通过解决这些问题来自定义实现分布式配置中心。

 

思考实现分布式配置中心有哪些问题?

1、在某个服务中如何获取统一的配置?
2、统一配置的定义有哪几种方式?
3、Spring 配置源的实现方式有哪些,优先级是怎样的?

image.png

1.5.1、在某个服务中如何获取统一的配置?

服务可以通过客户端发起 HttpClient 到统一配置中心上获取配置,下列是 Config 的 HTTP 路径访问获取配置。

例如:
/ { 应用名 } / { 环境名 } [ / { 分支名 } ]
/ { 应用名 } - { 环境名 }.yml
/ { 应用名 } - { 环境名 }.properties
/ { 分支名 } / { 应用名 } - { 环境名 }.yml
/ { 分支名 } / { 应用名 } - { 环境名 }.properties

下列是 config 的 rest 接口提供的部分源码

http.png

1.5.2、统一配置的定义有那几种方式?

配置源:文件 (file://)、HTTP(http://)、数据源(jdbc://)、Git(git://)、SVN

1.5.3、Spring 配置源的实现方式有哪些,优先级是怎样的?

1、Apache Commons Configuration

Apache Commons Configuration 包的扩展提供一套完整配置 API,并且能够通过组合模式多种不同的配置来源,来实现统一的配置读取,不足之处是不支持动态更新和写入。
Netflix Archaius 扩展了 Apache Commons Configuration,实现动态更新和写入等操作,不过同时它尚未提供类型转换的 API 实现。

  • Commons-configuration:包
  • Configuration:提供大多数常见的 Value 转换
    • PropertiesConfiguration:配置文件
    • XMLConfigration:XML 配置文件
    • DatabaseConfiguration:关系型数据库
    • SystemConfiguration:Java 系统属性
    • EnvironmentConfiguration:OS 环境变量
    • CompositeConfiguration:特殊组合实现(自身不提供数据来源,组合其他子来源,并控制优先级顺序)
//简单的实例说明
CompositeConfiguration
  DatabaseConfiguration:user.age = 18
  PropertiesConfiguration:user.age = 38
  XMLConfiguration:user.age = 30
  SystemConfiguration:user.age = 20

//CompositeConfiguration:Configuration.getInteger("user.age");
//从设计角度 获取第一个 18
//或者获取最后一个,上述的都被覆盖 20
String key = "user.age";
for(Configuraiton configuration : getConfigurations()){
    Integer value = configuration.getInteger(key);
    if(value !=null){
        return value;
    }
}

2、Spring 中 PropertySource:配置源

单配置源:org.springframework.core.env.PropertySource
多配置源:org.springframework.core.env.PropertySources
类型转换:org.springframework.core.convert.ConversionService

1.5.5.png

3、Spring 中 PropertySource:优先级的设定

采用类似双亲委派模式来定义的,下列由上往下加载,类似于 java 类加载器的加载模式

spring boot 配置来源:ConfigFileApplicationListener
spring cloud 配置来源:BootstrapApplicationListener

1.5.5.1.png

1.6、ConfigService 基于 JDBC 的源码分析

JDBCTemplate Bean 来源 JdbcTemplateAutoConfiguration

  • SQL 来源 JdbcEnvironmentPerperties
    • spring.cloud.config.server.jdbc.sql 不配置,默认:DEFALUT_SQL
      • DEFALUT_SQL:select key,value form properties where application=? and profile=? and lable = ?

1、@EnableConfigServer 入口就是这个注解

最主要的是 ConfigServerConfiguration.class,跟进

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Ducumented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer{
}

2、ConfigServerConfiguration 主要的配置的类

主要是注入 Marker 这个类,后续用到

@Configuration
public class ConfigServerConfiguration{
    class Marker{}
    @Bean
    public Marker enableConfigServerMarker(){
        return new Marker();
    }
}

3、springboot 的自动装载

因为导入了 spring-cloud-config-server.jar,所以  @EnableAutoConfiguration 注解会自动装载 ConfigServerAutoConfiguration 和 EnryptionAutoConfigutation 这两个配置类,主要分析第一个

4、ConfigServerSutoConfiguration 自动装载的类

从源码可以看出支持了多种配置源的实现:

  1. EnvironmentRepositoryConfiguration.class      #最主要的配置源实现
  2. CompositeConfiguration.class                           #参数配置的实现
  3. ResourceRepositoryConfiguration.class           #Resuource 资源访问的实现
  4. ConfigServerEncryptionConfiguration.class    #加解密的接口实现
  5. ConfigServerMvcConfiguration.class               #controller 对外提供 rest 接口的实现

上面提到的 ConfigServerConfiguration.Marker.class 在这里作为条件

@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class,
		ResourceRepositoryConfiguration.class, ConfigServerEncryptionConfiguration.class,
		ConfigServerMvcConfiguration.class })
public class ConfigServerAutoConfiguration {

}

5、EnvironmentRepositoryConfiguration.class 最主要的配置源实现

Jdbc 的实现源码是 JdbcRepositoryConfiguration.class 中,当然还有其他的一些实现,比如 svn,git 等

@Configuration
@EnableConfigurationProperties({ SvnKitEnvironmentProperties.class,
		CredhubEnvironmentProperties.class, JdbcEnvironmentProperties.class,
		NativeEnvironmentProperties.class, VaultEnvironmentProperties.class })
@Import({ CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class,
		VaultConfiguration.class, VaultRepositoryConfiguration.class,
		CredhubConfiguration.class, CredhubRepositoryConfiguration.class,
		SvnRepositoryConfiguration.class, NativeRepositoryConfiguration.class,
		GitRepositoryConfiguration.class, DefaultRepositoryConfiguration.class })
public class EnvironmentRepositoryConfiguration {

6、JdbcRepositoryConfiguration.class 生成 Jdbc 工厂类

@Configuration
@Profile("jdbc")
@ConditionalOnClass(JdbcTemplate.class)
class JdbcRepositoryConfiguration {

	@Bean
	@ConditionalOnBean(JdbcTemplate.class)
	public JdbcEnvironmentRepository jdbcEnvironmentRepository(
			JdbcEnvironmentRepositoryFactory factory,
			JdbcEnvironmentProperties environmentProperties) {
		return factory.build(environmentProperties);
	}

}

7、JdbcEnvironemntRepository 使用工厂类来创建

public class JdbcEnvironmentRepositoryFactory implements
		EnvironmentRepositoryFactory<JdbcEnvironmentRepository, JdbcEnvironmentProperties> {

	private JdbcTemplate jdbc;

	public JdbcEnvironmentRepositoryFactory(JdbcTemplate jdbc) {
		this.jdbc = jdbc;
	}

	@Override
	public JdbcEnvironmentRepository build(
			JdbcEnvironmentProperties environmentProperties) {
		return new JdbcEnvironmentRepository(this.jdbc, environmentProperties);
	}

}

8、findOne() 从数据库中获取配置信息返回

@Override
	public Environment findOne(String application, String profile, String label) {
		String config = application;
		if (StringUtils.isEmpty(label)) {
			label = "master";
		}
		if (StringUtils.isEmpty(profile)) {
			profile = "default";
		}
		if (!profile.startsWith("default")) {
			profile = "default," + profile;
		}
		String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
		Environment environment = new Environment(application, profiles, label, null,
				null);
		if (!config.startsWith("application")) {
			config = "application," + config;
		}
		List<String> applications = new ArrayList<String>(new LinkedHashSet<>(
				Arrays.asList(StringUtils.commaDelimitedListToStringArray(config))));
		List<String> envs = new ArrayList<String>(
				new LinkedHashSet<>(Arrays.asList(profiles)));
		Collections.reverse(applications);
		Collections.reverse(envs);
		for (String app : applications) {
			for (String env : envs) {
				Map<String, String> next = (Map<String, String>) this.jdbc.query(this.sql,
						new Object[] { app, env, label }, this.extractor);
				if (!next.isEmpty()) {
					environment.add(new PropertySource(app + "-" + env, next));
				}
			}
		}
		return environment;
	}

9、sql 默认DEFAULT_SQL,可以从配置文件中获取覆盖

@ConfigurationProperties("spring.cloud.config.server.jdbc")
public class JdbcEnvironmentProperties implements EnvironmentRepositoryProperties {

	private static final String DEFAULT_SQL = "SELECT KEY, VALUE from PROPERTIES"
			+ " where APPLICATION=? and PROFILE=? and LABEL=?";

	private int order = Ordered.LOWEST_PRECEDENCE - 10;

	/** SQL used to query database for keys and values. */
	private String sql = DEFAULT_SQL;

	public int getOrder() {
		return this.order;
	}

	@Override
	public void setOrder(int order) {
		this.order = order;
	}

	public String getSql() {
		return this.sql;
	}

	public void setSql(String sql) {
		this.sql = sql;
	}

}

1.7、原理本质说明

核心接口 EnvironmentRepository 的部分类图展示

如果我们想自定义一个配置源的实现,是否可以继承这个接口,最后自动转载就可以了呢?答案是可以的。

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值