对于线上的生产环境,通常对其都是有很高的要求,其中,高可用是不可或缺的一部分。必须保证服务是可用的,才能保证系统更好的运行,这是业务稳定的保证。 高可用一般分为两种:客户端高可用
、服务端高可用
客户端高可用 源码:https://gitee.com/laiyy0728/spring-cloud/tree/master/spring-cloud-config/spring-cloud-config-ha/spring-cloud-config-ha-client
客户端高可用
主要解决当前服务端不可用哪个的情况下,客户端依然可用正常启动。从客户端触发,不是增加配置中心的高可用性,而是降低客户端对配置中心的依赖程度,从而提高整个分布式架构的健壮性。
实现 配置的自动装配 pom.xml
1 2 3 4 5 6 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-client</artifactId > </dependency > </dependencies >
配置文件解析
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 @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 enabled = false ; private String fallbackLocation; public String getFallbackLocation () { return fallbackLocation; } public void setFallbackLocation (String fallbackLocation) { if (!fallbackLocation.contains("." )) { fallbackLocation = fallbackLocation.endsWith(File.separator) ? fallbackLocation : fallbackLocation + File.separator; fallbackLocation += DEFAULT_FILE_NAME; } this .fallbackLocation = fallbackLocation; } public boolean isEnabled () { return enabled; } public void setEnabled (boolean enabled) { this .enabled = enabled; } }
自动装配实现类
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 186 @Configuration @EnableConfigurationProperties (ConfigSupportProperties.class)public class ConfigSupportConfiguration implements ApplicationContextInitializer <ConfigurableApplicationContext >, Ordered { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Integer orderNumber = 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 Server 配置资源 <<<<<<<<<<<<<<<" ); ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment(); MutablePropertySources propertySources = environment.getPropertySources(); logger.info(">>>>>>>>>>>>> 加载 PropertySources 源:" + propertySources.size() + " 个" ); if (!configSupportProperties.isEnabled()) { logger.info(">>>>>>>>>>>>> 配置备份未启用,使用:{}.enabled 打开 <<<<<<<<<<<<<<" , ConfigSupportProperties.CONFIG_PREFIX); return ; } if (isCloudConfigLoaded(propertySources)) { PropertySource cloudConfigSource = getLoadedCloudPropertySource(propertySources); logger.info(">>>>>>>>>>>> 获取 config service 配置资源 <<<<<<<<<<<<<<<" ); Map<String, Object> backupPropertyMap = makeBackupPropertySource(cloudConfigSource); doBackup(backupPropertyMap, configSupportProperties.getFallbackLocation()); } else { logger.info(">>>>>>>>>>>>>> 获取 config Server 资源配置失败 <<<<<<<<<<<<<" ); Properties backupProperty = loadBackupProperty(configSupportProperties.getFallbackLocation()); if (backupProperty != null ) { Map backupSourceMap = new HashMap<>(backupProperty); PropertySource backupSource = new MapPropertySource("backupSource" , backupSourceMap); propertySources.addFirst(backupSource); } } } @Override public int getOrder () { return orderNumber; } private Properties loadBackupProperty (String fallbackLocation) { logger.info(">>>>>>>>>>>> 正在从本地加载!<<<<<<<<<<<<<<<<<" ); PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); Properties properties = new Properties(); try { FileSystemResource fileSystemResource = new FileSystemResource(fallbackLocation); propertiesFactoryBean.setLocation(fileSystemResource); propertiesFactoryBean.afterPropertiesSet(); properties = propertiesFactoryBean.getObject(); if (properties != null ){ logger.info(">>>>>>>>>>>>>>> 读取成功!<<<<<<<<<<<<<<<<<<<<<<<<" ); } }catch (Exception e){ e.printStackTrace(); return null ; } return properties; } private void doBackup (Map<String, Object> backupPropertyMap, String fallbackLocation) { FileSystemResource fileSystemResource = new FileSystemResource(fallbackLocation); File file = fileSystemResource.getFile(); try { if (!file.exists()){ file.createNewFile(); } if (!file.canWrite()){ logger.info(">>>>>>>>>>>> 文件无法写入:{} <<<<<<<<<<<<<<<" , fileSystemResource.getPath()); return ; } Properties properties = new Properties(); Iterator<String> iterator = backupPropertyMap.keySet().iterator(); while (iterator.hasNext()) { String key = iterator.next(); properties.setProperty(key, String.valueOf(backupPropertyMap.get(key))); } FileOutputStream fileOutputStream = new FileOutputStream(fileSystemResource.getFile()); properties.store(fileOutputStream, "backup cloud config" ); }catch (Exception e){ logger.info(">>>>>>>>>> 文件操作失败! <<<<<<<<<<<" ); e.printStackTrace(); } } private Map<String, Object> makeBackupPropertySource (PropertySource cloudConfigSource) { Map<String, Object> backupSourceMap = new HashMap<>(); if (cloudConfigSource instanceof CompositePropertySource) { CompositePropertySource propertySource = (CompositePropertySource) cloudConfigSource; for (PropertySource<?> source : propertySource.getPropertySources()) { if (source instanceof MapPropertySource){ MapPropertySource mapPropertySource = (MapPropertySource) source; String[] propertyNames = mapPropertySource.getPropertyNames(); for (String propertyName : propertyNames) { if (!backupSourceMap.containsKey(propertyName)) { backupSourceMap.put(propertyName, mapPropertySource.getProperty(propertyName)); } } } } } return backupSourceMap; } private boolean isHasCloudConfigLocator (List<PropertySourceLocator> propertySourceLocators) { for (PropertySourceLocator propertySourceLocator : propertySourceLocators) { if (propertySourceLocator instanceof ConfigServicePropertySourceLocator) { return true ; } } return false ; } 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 ("configService" .equals(source.getName())){ return source; } } } return null ; } private boolean isCloudConfigLoaded (MutablePropertySources propertySources) { return getLoadedCloudPropertySource(propertySources) != null ; } }
META-INF/spring.factories
1 2 org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.laiyy.gitee.confog.springcloudconfighaclientautoconfig.ConfigSupportConfiguration
客户端实现 1 2 3 4 5 6 7 8 9 10 11 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-client</artifactId > </dependency > <dependency > <groupId > com.laiyy.gitee.confog</groupId > <artifactId > spring-cloud-config-ha-client-autoconfig</artifactId > <version > 0.0.1-SNAPSHOT</version > </dependency > </dependencies >
bootstrap.yml
1 2 3 4 5 6 7 8 9 10 spring: cloud: config: label: master uri: http://localhost:9090 name: config-simple profile: dev backup: enabled: true fallbackLocation: D:/cloud
application.yml
1 2 3 4 5 6 server: port: 9015 spring: application: name: spring-cloud-config-ha-client-config
启动类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @SpringBootApplication @RestController public class SpringCloudConfigHaClientConfigApplication { public static void main (String[] args) { SpringApplication.run(SpringCloudConfigHaClientConfigApplication.class, args); } @Value ("${com.laiyy.gitee.config}" ) private String config; @GetMapping (value = "/config" ) public String getConfig () { return config; } }
config server 1 2 3 4 5 6 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-server</artifactId > </dependency > </dependencies >
1 2 3 4 5 6 7 8 9 server: port: 9090 spring: cloud: config: server: git: uri: https://gitee.com/laiyy0728/config-repo.git search-paths: config-simple
1 2 3 4 5 6 7 8 9 @EnableConfigServer @SpringBootApplication public class SpringCloudConfigHaClientConfigServerApplication { public static void main (String[] args) { SpringApplication.run(SpringCloudConfigHaClientConfigServerApplication.class, args); } }
验证 先后启动 config-server
、config-client
,查看config-client
控制台输出:
1 2 3 4 5 6 7 Fetching config from server at : http://localhost:9090 Located environment: name=config-simple, profiles=[dev], label=master, version=ee39bf20c492b27c2d1b1d0ff378ad721e79a758, state=null Located property source: CompositePropertySource {name='configService', propertySources=[MapPropertySource {name='configClient'}, MapPropertySource {name='https://gitee.com/laiyy0728/config-repo.git/config-simple/config-simple-dev.yml'}]} >>>>>>>>>>>>>>> 检查 config Server 配置资源 <<<<<<<<<<<<<<< >>>>>>>>>>>>> 加载 PropertySources 源:11 个 >>>>>>>>>>>> 获取 config service 配置资源 <<<<<<<<<<<<<<< No active profile set, falling back to default profiles: default
查看 d:/cloud
,可见存在 fallback.properties
文件,打开文件,可见配置信息如下:
1 2 3 4 #backup cloud config #Wed Apr 10 14:49:36 CST 2019 config.client.version=ee39bf20c492b27c2d1b1d0ff378ad721e79a758 com.laiyy.gitee.config=dev \u73AF\u5883\uFF0Cgit \u7248 spring cloud config-----\!
访问 http://localhost:9015/config ,可见打印信息如下:
停止 server
、client
,删除 d:/cloud/fallback.properties
,将 ConfigSupportConfiguration
的 orderNumber 改为 Ordered.HIGHEST_PRECEDENCE + 9
,再次先后启动 config-server
、config-client
,查看控制 client
控制台输出如下:
1 2 3 4 >>>>>>>>>>>>>>> 检查 config Server 配置资源 <<<<<<<<<<<<<<< >>>>>>>>>>>>> 加载 PropertySources 源:10 个 >>>>>>>>>>>>>> 获取 config Server 资源配置失败 <<<<<<<<<<<<< Fetching config from server at : http://localhost:9090
可见,PropertySources
源从原来的 11 个,变为 10 个。原因是 bootstrap.yml
的加载顺序问题。 在源码:org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
中,其加载顺序为:Ordered.HIGHEST_PRECEDENCE + 10
,而 ConfigSupportConfiguration
的加载顺序为 Ordered.HIGHEST_PRECEDENCE + 9
,先于 bootstrap.yml 配置文件加载执行,所以无法获取到远程配置信息,继而无法备份配置信息。
重新进行第一步验证,然后将 config-server
、config-client
停掉后,只启动 config-client
,可见其控制台打印信息如下:
1 2 3 4 5 >>>>>>>>>>>>>>> 检查 config Server 配置资源 <<<<<<<<<<<<<<< >>>>>>>>>>>>> 加载 PropertySources 源:10 个 >>>>>>>>>>>>>> 获取 config Server 资源配置失败 <<<<<<<<<<<<< >>>>>>>>>>>> 正在从本地加载!<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>> 读取成功!<<<<<<<<<<<<<<<<<<<<<<<<
访问 http://localhost:9015/config 正常返回信息。
由此验证客户端高可用
成功
服务端高可用 服务端高可用,一般情况下是通过与注册中心结合实现。通过 Ribbon 的负载均衡选择 Config Server 进行连接,来获取配置信息。
源码:https://gitee.com/laiyy0728/spring-cloud/tree/master/spring-cloud-config/spring-cloud-config-ha/spring-cloud-config-ha-server
eureka 选择使用 spring-cloud-eureka-server-simple
config server 1 2 3 4 5 6 7 8 9 10 11 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-config-server</artifactId > </dependency > </dependencies >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 spring: cloud: config: server: git: uri: https://gitee.com/laiyy0728/config-repo.git search-paths: config-simple application: name: spring-cloud-config-ha-server-app server: port: 9090 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @EnableConfigServer @EnableDiscoveryClient public class SpringCloudConfigHaServerConfigApplication { public static void main (String[] args) { SpringApplication.run(SpringCloudConfigHaServerConfigApplication.class, args); } }
config client 1 2 3 4 5 6 7 8 9 10 11 <dependencies > <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 > </dependencies >
application.yml
1 2 3 4 5 6 server: port: 9016 spring: application: name: spring-cloud-config-ha-server-client
bootstrap.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 spring: cloud: config: label: master name: config-simple profile: dev discovery: enabled: true service-id: spring-cloud-config-ha-server-app eureka: client: service-url: defauleZone: http://localhost:8761/eureka/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @SpringBootApplication @RestController public class SpringCloudConfigHaServerClientApplication { public static void main (String[] args) { SpringApplication.run(SpringCloudConfigHaServerClientApplication.class, args); } @Value ("${com.laiyy.gitee.config}" ) private String config; @GetMapping (value = "/config" ) public String getConfig () { return config; } }
启用验证:访问 http://localhost:9016/config ,返回值如下: