Spring cloud config
Spring cloud config 搭建过程参考 http://blog.didispace.com/springcloud4/在使用spring cloud 构建分布式系统的过程中,为了完成多个服务的配置统一管理,使用了spring cloud config作为配置中心,管理所有微服务的系统配置。
在分布式系统中,配置中心是一个独立的服务部件,作用是专门为其他服务提供系统启动所需的配置信息,保证系统正确启动。
使用中带来一个问题,即配置中心的高可用。
配置中心的高可用问题
配置中心(spring cloud config server)本身可以通过部署多个节点,并且通过服务注册中心(Eureka) 向其他服务系统提供服务。但是这种高可用在我看来本身并不可靠。
- 配置中心不是业务系统,不会有其他业务系统那么高的高可用实施优先级,节点数量,主机性能,稳定性都会有所差距。
- 配置中心可能又会依赖其他系统,如git,降低了高可用性,git一旦停止服务,则配置中心直接挂掉。
- 后果严重 ,配置中心一旦全部失效,会导致所有服务都无法正常启动
查看 PropertySourceBootstrapConfiguration源码 实现ApplicationContextInitializer接口
//ApplicationContextInitializer本质上是一个回调接口,用于在ConfigurableApplicationContext执行refresh操作之前对它进行一些初始化操作 PropertySourceLocator就是spring cloud config 提供的获取访问配置中心获取配置的方法。
可以看到,获取的结果被放入了composite中,并最终和本地的其他配置项合并public void initialize(ConfigurableApplicationContext applicationContext) { CompositePropertySource composite = new CompositePropertySource("bootstrapProperties"); AnnotationAwareOrderComparator.sort(this.propertySourceLocators); boolean empty = true; ConfigurableEnvironment environment = applicationContext.getEnvironment(); Iterator var5 = this.propertySourceLocators.iterator(); while(var5.hasNext()) { PropertySourceLocator locator = (PropertySourceLocator)var5.next(); PropertySource<?> source = null; source = locator.locate(environment); if(source != null) { logger.info("Located property source: " + source); composite.addPropertySource(source); empty = false; } } if(!empty) { MutablePropertySources propertySources = environment.getPropertySources(); String logConfig = environment.resolvePlaceholders("${logging.config:}"); LogFile logFile = LogFile.get(environment); if(propertySources.contains("bootstrapProperties")) { propertySources.remove("bootstrapProperties"); } this.insertPropertySources(propertySources, composite); this.reinitializeLoggingSystem(environment, logConfig, logFile); this.setLogLevels(environment); } }
我们根据原始方法改写 使用maven新建项目 spring-cloud-config-support在pom.xml 中加入依赖
<groupId>com.chengzhi</groupId> <artifactId>spring-cloud-config-support</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Brixton.SR6</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.13</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <optional>true</optional> </dependency> </dependencies>
新建 CloudConfigSupportConfiguration.java
package com.chengzhi.support.configuration;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.boot.bind.PropertySourcesPropertyValues; import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration; import org.springframework.cloud.bootstrap.config.PropertySourceLocator; import org.springframework.cloud.config.client.ConfigServicePropertySourceLocator; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.env.*; import org.springframework.core.io.FileSystemResource; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.*; @Configuration @EnableConfigurationProperties(CloudConfigSupportProperties.class) public class CloudConfigSupportConfiguration implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered { private static Logger logger = LoggerFactory.getLogger(CloudConfigSupportConfiguration.class); //order越小启动优先级越高 private int order = Ordered.HIGHEST_PRECEDENCE + 11; @Autowired(required = false) private List<PropertySourceLocator> propertySourceLocators = Collections.EMPTY_LIST; @Override public void initialize(ConfigurableApplicationContext applicationContext) { if (!isHasCloudConfigLocator(this.propertySourceLocators)) { logger.info("未启用Config Server管理配置"); return; } logger.info("检查Config Service配置资源"); ConfigurableEnvironment environment = applicationContext.getEnvironment(); MutablePropertySources propertySources = environment.getPropertySources(); logger.info("加载PropertySources源:" + propertySources.size() + "个"); CloudConfigSupportProperties configSupportProperties = new CloudConfigSupportProperties(); new RelaxedDataBinder(configSupportProperties, CloudConfigSupportProperties.CONFIG_PREFIX) .bind(new PropertySourcesPropertyValues(propertySources)); if (!configSupportProperties.isEnable()) { logger.warn("未启用配置备份功能,可使用{}.enable打开", CloudConfigSupportProperties.CONFIG_PREFIX); return; } if (isCloudConfigLoaded(propertySources)) { PropertySource cloudConfigSource = getLoadedCloudPropertySource(propertySources); logger.info("成功获取ConfigService配置资源"); //备份 Map<String, Object> backupPropertyMap = makeBackupPropertyMap(cloudConfigSource); doBackup(backupPropertyMap, configSupportProperties.getFile()); } else { logger.error("获取ConfigService配置资源失败"); Properties backupProperty = loadBackupProperty(configSupportProperties.getFile()); if (backupProperty != null) { HashMap backupSourceMap = new HashMap<>(backupProperty); PropertySource backupSource = new MapPropertySource("backupSource", backupSourceMap); propertySources.addFirst(backupSource); logger.warn("使用备份的配置启动:{}", configSupportProperties.getFile()); } } } /** * 是否启用了Spring Cloud Config获取配置资源 * * @param propertySourceLocators * @return */ private boolean isHasCloudConfigLocator(List<PropertySourceLocator> propertySourceLocators) { for (PropertySourceLocator sourceLocator : propertySourceLocators) { if (sourceLocator instanceof ConfigServicePropertySourceLocator) { return true; } } return false; } /** * 是否启用Cloud Config * * @param propertySources * @return */ private boolean isCloudConfigLoaded(MutablePropertySources propertySources) { if (getLoadedCloudPropertySource(propertySources) == null) { return false; } return true; } /** * 获取加载的Cloud Config 配置项 * * @param propertySources * @return */ private PropertySource getLoadedCloudPropertySource(MutablePropertySources propertySources) { if (!propertySources.contains(PropertySourceBootstrapConfiguration.BOOTSTRAP_PROPERTY_SOURCE_NAME)) { return null; } PropertySource propertySource = propertySources.get(PropertySourceBootstrapConfiguration.BOOTSTRAP_PROPERTY_SOURCE_NAME); if (propertySource instanceof CompositePropertySource) { for (PropertySource<?> source : ((CompositePropertySource) propertySource).getPropertySources()) { if (source.getName().equals("configService")) { return source; } } } return null; } /** * 生成备份的配置数据 * * @param propertySource * @return */ private Map<String, Object> makeBackupPropertyMap(PropertySource propertySource) { // PropertySource backupSource = new MapPropertySource("backupSource", backupSourceMap); Map<String, Object> backupSourceMap = new HashMap<>(); if (propertySource instanceof CompositePropertySource) { CompositePropertySource composite = (CompositePropertySource) propertySource; for (PropertySource<?> source : composite.getPropertySources()) { if (source instanceof MapPropertySource) { MapPropertySource mapSource = (MapPropertySource) source; for (String propertyName : mapSource.getPropertyNames()) { // 前面的配置覆盖后面的配置 if (!backupSourceMap.containsKey(propertyName)) { backupSourceMap.put(propertyName, mapSource.getProperty(propertyName)); } } } } } return backupSourceMap; } private void doBackup(Map<String, Object> backupPropertyMap, String filePath) { FileSystemResource fileSystemResource = new FileSystemResource(filePath); File backupFile = fileSystemResource.getFile(); try { if (!backupFile.exists()) { backupFile.createNewFile(); } if (!backupFile.canWrite()) { logger.error("无法读写文件:{}", fileSystemResource.getPath()); } Properties properties = new Properties(); Iterator<String> keyIterator = backupPropertyMap.keySet().iterator(); while (keyIterator.hasNext()) { String key = keyIterator.next(); properties.setProperty(key, String.valueOf(backupPropertyMap.get(key))); } FileOutputStream fos = new FileOutputStream(fileSystemResource.getFile()); properties.store(fos, "Backup Cloud Config"); } catch (IOException e) { logger.error("文件操作失败:{}", fileSystemResource.getPath()); e.printStackTrace(); } } private Properties loadBackupProperty(String filePath) { PropertiesFactoryBean propertiesFactory = new PropertiesFactoryBean(); Properties props = new Properties(); try { FileSystemResource fileSystemResource = new FileSystemResource(filePath); propertiesFactory.setLocation(fileSystemResource); propertiesFactory.afterPropertiesSet(); props = propertiesFactory.getObject(); } catch (IOException e) { e.printStackTrace(); return null; } return props; } @Override public int getOrder() { return this.order; } }
再建个 CloudConfigSupportProperties 类用来加载我们的配置前缀
package com.chengzhi.support.configuration;import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(CloudConfigSupportProperties.CONFIG_PREFIX) public class CloudConfigSupportProperties { public static final String CONFIG_PREFIX = "spring.cloud.config.backup"; private boolean enable = false; private String file = "backup.properties"; public boolean isEnable() { return enable; } public void setEnable(boolean enable) { this.enable = enable; } public String getFile() { return file; } public void setFile(String file) { this.file = file; } }
服务启动是要加载我们的配置 在resource下面新建 META-INF/spring.factories加入我们的配置文件路径org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.chengzhi.support.configuration.CloudConfigSupportConfiguration\
测试 在我们要依赖的项目加入依赖
在配置文件中加入
spring.cloud.config.backup.enable=true
spring.cloud.config.backup.file=/backup.properties
启动项目会发现我们项目中或多个backup.properties 文件内容与Spring-Config配置中心文件一致
这样就算配置中心挂了我们也一样可以启动项目
项目地址
Spring Cloud Config 客户端的高可用实现

最新推荐文章于 2023-04-14 16:09:31 发布
