對(duì)于線上的生產(chǎn)環(huán)境,通常對(duì)其都是有很高的要求搪搏,其中,高可用是不可或缺的一部分闪金。必須保證服務(wù)是可用的,才能保證系統(tǒng)更好的運(yùn)行论颅,這是業(yè)務(wù)穩(wěn)定的保證哎垦。
高可用一般分為兩種:客戶端高可用
、服務(wù)端高可用
客戶端高可用
客戶端高可用
主要解決當(dāng)前服務(wù)端不可用哪個(gè)的情況下恃疯,客戶端依然可用正常啟動(dòng)漏设。從客戶端觸發(fā),不是增加配置中心的高可用性今妄,而是降低客戶端對(duì)配置中心的依賴程度郑口,從而提高整個(gè)分布式架構(gòu)的健壯性。
實(shí)現(xiàn)
配置的自動(dòng)裝配
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
</dependencies>
配置文件解析
@Component
@ConfigurationProperties(prefix = ConfigSupportProperties.CONFIG_PREFIX)
public class ConfigSupportProperties {
/**
* 加載的配置文件前綴
*/
public static final String CONFIG_PREFIX = "spring.cloud.config.backup";
/**
* 默認(rèn)文件名
*/
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(".")) {
// 如果只指定了文件路徑盾鳞,自動(dòng)拼接文件名
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;
}
}
自動(dòng)裝配實(shí)現(xiàn)類
@Configuration
@EnableConfigurationProperties(ConfigSupportProperties.class)
public class ConfigSupportConfiguration implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 重中之重H浴!L诮觥乒裆!
* 一定要注意加載順序!M评鹤耍!
*
* bootstrap.yml 加載類:org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration 的加載順序是
* HIGHEST_PRECEDENCE+10肉迫,
* 如果當(dāng)前配置類再其之前加載,無(wú)法找到 bootstrap 配置文件中的信息稿黄,繼而無(wú)法加載到本地
* 所以當(dāng)前配置類的裝配順序一定要在 PropertySourceBootstrapConfiguration 之后喊衫!
*/
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) {
// 判斷是否開啟 config server 管理配置
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() + " 個(gè)");
// 判斷配置備份功能是否啟用
if (!configSupportProperties.isEnabled()) {
logger.info(">>>>>>>>>>>>> 配置備份未啟用,使用:{}.enabled 打開 <<<<<<<<<<<<<<", ConfigSupportProperties.CONFIG_PREFIX);
return;
}
if (isCloudConfigLoaded(propertySources)) {
// 可以從 spring cloud 中獲取配置信息
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(">>>>>>>>>>>> 文件無(wú)法寫入:{} <<<<<<<<<<<<<<<", 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();
}
}
/**
* 將配置信息轉(zhuǎn)換為 map
*/
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;
}
/**
* config server 管理配置是否開啟
*/
private boolean isHasCloudConfigLocator(List<PropertySourceLocator> propertySourceLocators) {
for (PropertySourceLocator propertySourceLocator : propertySourceLocators) {
if (propertySourceLocator instanceof ConfigServicePropertySourceLocator) {
return true;
}
}
return false;
}
/**
* 獲取 config service 配置資源
*/
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()) {
// 如果配置源是 config service,使用此配置源獲取配置信息
// configService 是 bootstrapProperties 加載 spring cloud 的實(shí)現(xiàn):ConfigServiceBootstrapConfiguration
if ("configService".equals(source.getName())){
return source;
}
}
}
return null;
}
/**
* 判斷是否可以從 spring cloud 中獲取配置信息
*/
private boolean isCloudConfigLoaded(MutablePropertySources propertySources) {
return getLoadedCloudPropertySource(propertySources) != null;
}
}
META-INF/spring.factories
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.laiyy.gitee.confog.springcloudconfighaclientautoconfig.ConfigSupportConfiguration
客戶端實(shí)現(xiàn)
<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
spring:
cloud:
config:
label: master
uri: http://localhost:9090
name: config-simple
profile: dev
backup:
enabled: true # 自定義配置 -- 是否啟用客戶端高可用配置
fallbackLocation: D:/cloud # 自動(dòng)備份的配置文檔存放位置
application.yml
server:
port: 9015
spring:
application:
name: spring-cloud-config-ha-client-config
啟動(dòng)類
@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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
server:
port: 9090
spring:
cloud:
config:
server:
git:
uri: https://gitee.com/laiyy0728/config-repo.git
search-paths: config-simple
@EnableConfigServer
@SpringBootApplication
public class SpringCloudConfigHaClientConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudConfigHaClientConfigServerApplication.class, args);
}
}
驗(yàn)證
先后啟動(dòng) config-server
撑教、config-client
朝墩,查看config-client
控制臺(tái)輸出:
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 個(gè)
>>>>>>>>>>>> 獲取 config service 配置資源 <<<<<<<<<<<<<<<
No active profile set, falling back to default profiles: default
查看 d:/cloud
,可見存在 fallback.properties
文件伟姐,打開文件收苏,可見配置信息如下:
#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
秆乳,再次先后啟動(dòng) config-server
懦鼠、config-client
,查看控制 client
控制臺(tái)輸出如下:
>>>>>>>>>>>>>>> 檢查 config Server 配置資源 <<<<<<<<<<<<<<<
>>>>>>>>>>>>> 加載 PropertySources 源:10 個(gè)
>>>>>>>>>>>>>> 獲取 config Server 資源配置失敗 <<<<<<<<<<<<<
Fetching config from server at : http://localhost:9090
可見屹堰,PropertySources
源從原來(lái)的 11 個(gè)肛冶,變?yōu)?10 個(gè)。原因是 bootstrap.yml
的加載順序問題扯键。
在源碼:org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
中睦袖,其加載順序?yàn)椋?code>Ordered.HIGHEST_PRECEDENCE + 10,而 ConfigSupportConfiguration
的加載順序?yàn)?Ordered.HIGHEST_PRECEDENCE + 9
荣刑,先于 bootstrap.yml 配置文件加載執(zhí)行馅笙,所以無(wú)法獲取到遠(yuǎn)程配置信息,繼而無(wú)法備份配置信息厉亏。
重新進(jìn)行第一步驗(yàn)證董习,然后將 config-server
、config-client
停掉后爱只,只啟動(dòng) config-client
阱飘,可見其控制臺(tái)打印信息如下:
>>>>>>>>>>>>>>> 檢查 config Server 配置資源 <<<<<<<<<<<<<<<
>>>>>>>>>>>>> 加載 PropertySources 源:10 個(gè)
>>>>>>>>>>>>>> 獲取 config Server 資源配置失敗 <<<<<<<<<<<<<
>>>>>>>>>>>> 正在從本地加載!<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>> 讀取成功!<<<<<<<<<<<<<<<<<<<<<<<<
訪問 http://localhost:9015/config 正常返回信息沥匈。
由此驗(yàn)證客戶端高可用
成功
服務(wù)端高可用
服務(wù)端高可用蔗喂,一般情況下是通過與注冊(cè)中心結(jié)合實(shí)現(xiàn)。通過 Ribbon 的負(fù)載均衡選擇 Config Server 進(jìn)行連接高帖,來(lái)獲取配置信息缰儿。
eureka 選擇使用 spring-cloud-eureka-server-simple
config server
<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>
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/
@SpringBootApplication
@EnableConfigServer
@EnableDiscoveryClient
public class SpringCloudConfigHaServerConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudConfigHaServerConfigApplication.class, args);
}
}
config client
<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
server:
port: 9016
spring:
application:
name: spring-cloud-config-ha-server-client
bootstrap.yml
spring:
cloud:
config:
label: master
name: config-simple
profile: dev
discovery:
enabled: true # 是否從注冊(cè)中心獲取 config server
service-id: spring-cloud-config-ha-server-app # 注冊(cè)中心 config server 的 serviceId
eureka:
client:
service-url:
defauleZone: http://localhost:8761/eureka/
@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;
}
}
啟用驗(yàn)證:訪問 http://localhost:9016/config ,返回值如下: