Spring Cloud Config 客戶端的高可用實現

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配置中心文件一致 
這樣就算配置中心掛了我們也一樣可以啓動項目

項目地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章