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 客戶端的高可用實現
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.