Spring Cloud Config 客户端的高可用实现

Spring Cloud 配置中心备份方案
本文介绍了一种在Spring Cloud中实现配置中心备份的方法,确保即使配置中心出现故障,也能通过备份文件正常启动应用。
 

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配置中心文件一致 
这样就算配置中心挂了我们也一样可以启动项目

项目地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值