大纲 大纲 前言 版本说明 在下面的的教程中,使用的 Spring Cloud 版本是 Finchley.RELEASE,对应的 Spring Boot 版本是 2.0.3,特别声明除外。
Config 高可用 对于线上的生产环境,通常对其都是有很高的要求,其中高可用是不可或缺的一部分,必须要保证服务是可用状态,才能保证系统更好地运行,这是业务稳定的保证。
Config 客户端高可用 对于客户端的高可用,这里的方案主要还是用 File 的形式,本质与 “客户端回退” 的思路大体一致。客户端高可用主要是解决当服务端不可用的情况下,客户端依然可以正常启动。从客户端的角度出发,不是增加配置中心的高可用性,而是降低客户端对配置中心的依赖程度,从而提高整个分布式架构的健壮性。客户端加载配置的高可用流程图如下,点击下载 完整的案例代码。
1. 准备工作 由于下面的 Spring Cloud Config 使用 Git 作为存储方式,因此需要提前在 Git 远程仓库(Github、Gitlab)中创建对应的仓库,然后往仓库里 Push 三个配置文件,分别是 config-client-dev.yml、config-client-prod.yml、config-client-test.yml,配置文件的内容如下:
1 2 3 4 5 6 server: port: 9001 cn: springcloud: config: I am the git configuration file from dev environment
1 2 3 4 5 6 server: port: 9002 cn: springcloud: config: I am the git configuration file from prod environment
1 2 3 4 5 6 server: port: 9003 cn: springcloud: config: I am the git configuration file from test environment
2. 创建 Config Client HA AutoConfig 工程 创建 Config Client HA AutoConfig 工程,配置工程里的 pom.xml
文件:
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-client</artifactId > </dependency >
创建 Config Client HA AutoConfig 工程里的配置属性加载类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Component @ConfigurationProperties(prefix = ConfigSupportProperties.CONFIG_PREFIX) public class ConfigSupportProperties { public static final String CONFIG_PREFIX = "spring.cloud.config.backup" ; private final String DEFAULT_FILE_NAME = "fallback.properties" ; private boolean enable = false ; private String fallbackLocation; public boolean isEnable () { return enable; } public void setEnable (boolean enable) { this .enable = enable; } public String getFallbackLocation () { return fallbackLocation; } public void setFallbackLocation (String fallbackLocation) { if (fallbackLocation.indexOf("." ) == -1 ) { this .fallbackLocation = fallbackLocation + DEFAULT_FILE_NAME; return ; } this .fallbackLocation = fallbackLocation; } }
创建 Config Client HA AutoConfig 工程里的自动配置类,该类主要的作用是判断 Config Server 端的配置信息是否可用,如果不能用将读取加载本地备份配置文件进行启动。需要注意的是启动顺序的设置,这是因为 Spring Cloud 使用的 PropertySourceBootstrapConfiguration 启动顺序为 private int order = -2147483638
,order 的值越小越先加载,所以下述的 orderNum 只要加上一个整数比其大即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 @Configuration @EnableConfigurationProperties(ConfigSupportProperties.class) public class ConfigSupportConfiguration implements ApplicationContextInitializer <ConfigurableApplicationContext >, Ordered { private final Logger LOGGER = LoggerFactory.getLogger(ConfigSupportConfiguration.class); private final Integer orderNum = Ordered.HIGHEST_PRECEDENCE + 11 ; @Autowired(required = false) private List<PropertySourceLocator> propertySourceLocators = Collections.EMPTY_LIST; @Autowired private ConfigSupportProperties configSupportProperties; @Override public void initialize (ConfigurableApplicationContext configurableApplicationContext) { if (!isHasCloudConfigLocator(this .propertySourceLocators)) { LOGGER.info("未启用Config Server管理配置" ); return ; } LOGGER.info("检查Config Service配置资源" ); ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment(); MutablePropertySources propertySources = environment.getPropertySources(); LOGGER.info("加载PropertySources源:" + propertySources.size() + "个" ); if (!configSupportProperties.isEnable()) { LOGGER.warn("未启用配置备份功能,可使用{}.enable打开" , ConfigSupportProperties.CONFIG_PREFIX); return ; } if (isCloudConfigLoaded(propertySources)) { PropertySource cloudConfigSource = getLoadedCloudPropertySource(propertySources); LOGGER.info("成功获取ConfigService配置资源" ); Map<String, Object> backupPropertyMap = makeBackupPropertyMap(cloudConfigSource); doBackup(backupPropertyMap, configSupportProperties.getFallbackLocation()); LOGGER.info("成功备份ConfigService配置资源" ); } else { LOGGER.error("获取ConfigService配置资源失败" ); Properties backupProperty = loadBackupProperty(configSupportProperties.getFallbackLocation()); if (backupProperty != null ) { HashMap backupSourceMap = new HashMap<>(backupProperty); PropertySource backupSource = new MapPropertySource("backupSource" , backupSourceMap); propertySources.addFirst(backupSource); LOGGER.warn("使用备份的配置启动:{}" , configSupportProperties.getFallbackLocation()); } } } @Override public int getOrder () { return orderNum; } private boolean isHasCloudConfigLocator (List<PropertySourceLocator> propertySourceLocators) { for (PropertySourceLocator sourceLocator : propertySourceLocators) { if (sourceLocator instanceof ConfigServicePropertySourceLocator) { return true ; } } return false ; } private boolean isCloudConfigLoaded (MutablePropertySources propertySources) { if (getLoadedCloudPropertySource(propertySources) == null ) { return false ; } return true ; } 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 ; } private Map<String, Object> makeBackupPropertyMap (PropertySource propertySource) { 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; } }
创建 Config Client HA AutoConfig 工程里 /src/main/resources/META-INF/spring.factories
配置文件,添加上面的自动配置类:
1 2 org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.springcloud.study.config.ConfigSupportConfiguration
3. 创建 Config Client 工程 创建 Config Client 的 Maven 工程,配置工程里的 pom.xml
文件,需要引入上面的 config-client-ha-autoconfig
:
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-client</artifactId > </dependency > <dependency > <groupId > com.springcloud.study</groupId > <artifactId > config-client-ha-autoconfig</artifactId > <version > 1.0-SNAPSHOT</version > </dependency >
创建 Config Client 的主启动类:
1 2 3 4 5 6 7 @SpringBootApplication public class ConfigClientApplication { public static void main (String[] args) { SpringApplication.run(ConfigClientApplication.class, args); } }
为了更好地观察拉取到的 Git 上面的配置,这里需要创建一个 Controller 用于访问返回配置信息,同时还需要创建一个实体,用于注入远程配置上的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Component @ConfigurationProperties(prefix = "cn.springcloud") public class ConfigProperties { private String config; public String getConfig () { return config; } public void setConfig (String config) { this .config = config; } }
1 2 3 4 5 6 7 8 9 10 11 @RestController public class ConfigController { @Autowired public ConfigProperties configProperties; @GetMapping("/getConfigInfo") public String getConfigInfo () { return configProperties.getConfig(); } }
添加 Config Client 需要的 application.yml
配置文件到工程中:
1 2 3 spring: application: name: config-client
添加 Config Client 需要的 bootstrap.yml
配置文件到工程中,enable
表示是否启动加载远程配置信息进行本地备份,fallbackLocation
表示本地备份的路径,也可以是路径加上文件名:
1 2 3 4 5 6 7 8 9 10 spring: cloud: config: name: config-client profile: dev label: master uri: http://127.0.0.1:8001 backup: enable: true fallbackLocation: /tmp/config/config-client-dev/fallback.properties
4. 创建 Config Server 工程 创建 Config Server 的 Maven 工程,配置工程里的 pom.xml
文件,需要引入 spring-cloud-config-server
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-server</artifactId > </dependency >
创建 Config Server 的主启动类,增加 @EnableConfigServer
注解:
1 2 3 4 5 6 7 8 @EnableConfigServer @SpringBootApplication public class ConfigServerApplication { public static void main (String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
添加 Config Server 需要的 application.yml
配置文件到工程中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 server: port: 8001 spring: application: name: config-server cloud: config: server: git: uri: git@github.com:xxxxx/spring-cloud-config-study-repo.git search-paths: spring-cloud-config-study-repo/ strictHostKeyChecking: false private_key_file: /root/.ssh/id_rsa.pub label: master
5. 测试结果 依次启动 config-server、config-client 应用 访问 http://127.0.0.1:9001/getConfigInfo
后观察返回的配置信息,与此同时查看是否在目录 /tmp/config/config-client-dev/
下成功创建了备份文件 fallback.properties
关闭 config-server、config-client 应用,然后单独启动 config-client 应用;观察在不启动 config-server 的情况下,config-client 应用是否能正常启动 若 config-client 应用单独启动成功,config-client 应用会先尝试去连接 config-server,当连接失败后,会加载本地的备份文件,此时控制台输出的日志信息如下: 1 2 3 4 5 6 7 c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://127.0.0.1:8001 c.c.c.ConfigServicePropertySourceLocator : Connect Timeout Exception on Url - http://127.0.0.1:8001. Will be trying the next url if available c.c.c.ConfigServicePropertySourceLocator : Could not locate PropertySource: I/O error on GET request for "http://127.0.0.1:8001/config-client/dev/master": 拒绝连接; nested exception is java.net.ConnectException: 拒绝连接 c.s.s.config.ConfigSupportConfiguration : 检查Config Service配置资源 c.s.s.config.ConfigSupportConfiguration : 加载PropertySources源:10个 c.s.s.config.ConfigSupportConfiguration : 获取ConfigService配置资源失败 c.s.s.config.ConfigSupportConfiguration : 使用备份的配置启动:/tmp/config/config-client-dev/fallback.properties
Config 服务端高可用 Config Server 一样需要在生成环境下保证高可用的,这里将通过结合 Eureka 注册中心的方式搭建 Config Server 的高可用,即通过 Ribbon 的客户端负载均衡选择一个 Config Server 进行连接来获取配置信息,具体的流程如下,点击下载 完整的案例代码。对于 Eureka 的高可用这里也不进行详解,详细关于 Eureka 的高可用可参考 Eureka 集群配置 。
1. 准备说明 本示例用到上面的 “客户端高可用” 示例中 Git 仓库里的配置文件,包括 config-client-dev.yml、config-client-prod.yml、config-client-test.yml。
2. 创建 Eureka Server 工程 创建 Eureka Server 的 Maven 工程,配置工程里的 pom.xml 文件,引入 spring-cloud-starter-netflix-eureka-server
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-server</artifactId > </dependency >
创建 Eureka Server 的启动主类,这里添加相应注解,作为程序的入口:
1 2 3 4 5 6 7 8 @EnableEurekaServer @SpringBootApplication public class EurekaServerApplication { public static void main (String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
添加 Eureka Server 需要的 application.yml
配置文件到工程的 src/main/resources
目录下:
1 2 3 4 5 6 7 8 9 10 11 server: port: 7001 eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
3. 创建 Config Server 工程 创建 Config Server 的 Maven 工程,配置工程里的 pom.xml
文件,由于 Config Sever 需要注册到 Eureka Server,所以需要另外添加 Eureka Client 的依赖:
1 2 3 4 5 6 7 8 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-server</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency >
创建 Config Server 的主启动类,增加 @EnableConfigServer
、@EnableDiscoveryClient
注解:
1 2 3 4 5 6 7 8 9 @EnableConfigServer @EnableDiscoveryClient @SpringBootApplication public class ConfigServerApplication { public static void main (String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
添加 Config Server 需要的 application.yml
配置文件到工程中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 server: port: 8001 spring: application: name: config-server cloud: config: server: git: uri: git@github.com:xxxxx/spring-cloud-config-study-repo.git search-paths: spring-cloud-config-study-repo/ strictHostKeyChecking: false private_key_file: /root/.ssh/id_rsa.pub label: master eureka: client: service-url: defaultZone: http://localhost:7001/eureka instance: instance-id: ${spring.application.name}-${server.port} prefer-ip-address: true
4. 创建 Config Client 工程 创建 Config Client 的 Maven 工程,配置工程里的 pom.xml
文件,由于 Config Client 需要注册到 Eureka Server,所以需要另外添加 Eureka Client 的依赖:
1 2 3 4 5 6 7 8 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-client</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency >
创建 Config Client 的主启动类,增加 @EnableDiscoveryClient
注解:
1 2 3 4 5 6 7 8 9 @EnableDiscoveryClient @SpringBootApplication public class ConfigClientApplication { public static void main (String[] args) { SpringApplication.run(ConfigClientApplication.class, args); } }
为了更好地观察拉取到的 Git 上面的配置,这里需要创建一个 Controller 用于访问返回配置信息,同时还需要创建一个实体,用于注入远程配置上的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Component @ConfigurationProperties(prefix = "cn.springcloud") public class ConfigProperties { private String config; public String getConfig () { return config; } public void setConfig (String config) { this .config = config; } }
1 2 3 4 5 6 7 8 9 10 11 @RestController public class ConfigController { @Autowired public ConfigProperties configProperties; @GetMapping("/getConfigInfo") public String getConfigInfo () { return configProperties.getConfig(); } }
添加 Config Client 需要的 application.yml
配置文件到工程中:
1 2 3 spring: application: name: config-client
添加 Config Client 需要的 bootstrap.yml
配置文件到工程中,这里不再使用 spring.cloud.config.uri
参数直接指向 Config Server 端的连接地址,而是增加了下述三个参数:
spring.cloud.config.discovery.enabled:开启 Config Client 的服务发现支持 spring.cloud.config.discovery.service-id:指定 Config Server 端的 serviceId,也就是 Config Server 端的 spring.application.name
参数值 eureka.client.service-url.defaultZone: 指向 Eureka 注册中心的地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 spring: cloud: config: name: config-client profile: dev label: master discovery: enabled: true service-id: config-server eureka: client: service-url: defaultZone: http://localhost:7001/eureka instance: instance-id: config-client-${server.port} prefer-ip-address: true
5. 测试 通过 maven install
命令将各个应用安装到本地,然后再使用命令行启动各个应用,当然也可以直接在 IDEA、Eclipse 里启动,具体的命令如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 java -jar eureka-server-1.0-SNAPSHOT.jar java -jar config-server-1.0-SNAPSHOT.jar java -jar config-server-1.0-SNAPSHOT.jar --server .port=8002 java -jar config-client-1.0-SNAPSHOT.jar java -jar config-client-1.0-SNAPSHOT.jar --server .port=9002
当两个 Config Client 应用启动完成后,查看控制台输出的日志信息,看看是否已经负载了;如果没有负载到也没关系,可以启动多个 Config Client 实例再试试 浏览器访问 http://127.0.0.1:9001/getConfigInfo
、http://127.0.0.1:9002/getConfigInfo
,观察是否可以正确返回配置信息 Config 源码解析(待续)