Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等)。使用起来也较 为简单。Consul使用Go语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与Docker等轻量级容器可无缝配合 。
consul配置一般设置又一份公共配置(公共配置节点名称:common-config)
spring.cloud.consul.config.default-context=common-config
和一份项目配置(项目名称:xxx-payable-service)
spring.application.name=xxx-payable-service
配置节点(data-key默认data)
spring.cloud.consul.config.data-key=data
consul中这个data-key是项目配置和公共配置共用的,consul中没有做分离,如果有多个项目共用到这个公共配置,那就不好做版本管理,这里介绍的就是针对这个问题做扩展,是项目配置和公共配置可以分离开来做管理
consul包中并没与提供扩展接口,这时需要用到spring的PropertySourceLocator接口
spring外部配置文件加载入口:
@Autowired(required = false)
private List propertySourceLocators = new ArrayList<>();
@Override
public int getOrder() {
return this.order;
}
public void setPropertySourceLocators(
Collection propertySourceLocators) {
this.propertySourceLocators = new ArrayList<>(propertySourceLocators);
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
CompositePropertySource composite = new CompositePropertySource(
BOOTSTRAP_PROPERTY_SOURCE_NAME);
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
// 遍历所有的PropertySourceLocator
for (PropertySourceLocator locator : this.propertySourceLocators) {
PropertySource> source = null;
// 执行locate方法,读取外部配置文件
source = locator.locate(environment);
if (source == null) {
continue;
}
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(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
}
// 将配置文件中的属性绑定
insertPropertySources(propertySources, composite);
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
handleIncludedProfiles(environment);
}
}
扩展类:
package com.dh.consul.config;
import com.ecwid.consul.v1.ConsulClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.cloud.consul.config.ConsulConfigProperties;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.retry.annotation.Retryable;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.util.*;
import static org.springframework.cloud.consul.config.ConsulConfigProperties.Format.FILES;
/**
* 加载consul default-context节点的配置文件(公共配置文件)
* @author dinghua
* @date 2019/10/14
* @since v1.0.0
*/
@Order(1)
public class ParentConsulPropertySourceLocator implements PropertySourceLocator {
private static final Logger log = LoggerFactory.getLogger(ParentConsulPropertySourceLocator.class);
private final ConsulClient consul;
private final ConsulConfigProperties properties;
/**
* parent默认data节点
*/
@Value("${fpx.consul.parent.config.data-key:V1.0.0}")
private String parentDataKey;
private final List contexts = new ArrayList<>();
private final LinkedHashMap contextIndex = new LinkedHashMap<>();
public ParentConsulPropertySourceLocator(ConsulClient consul,
ConsulConfigProperties properties) {
this.consul = consul;
this.properties = properties;
}
public LinkedHashMap getContextIndexes() {
return this.contextIndex;
}
@Override
@Retryable(interceptor = "consulRetryInterceptor")
public PropertySource> locate(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
List profiles = Arrays.asList(env.getActiveProfiles());
String prefix = this.properties.getPrefix();
List suffixes = new ArrayList<>();
if (this.properties.getFormat() != FILES) {
suffixes.add("/");
}
else {
suffixes.add(".yml");
suffixes.add(".yaml");
suffixes.add(".properties");
}
String defaultContext = getContext(prefix,
this.properties.getDefaultContext());
for (String suffix : suffixes) {
this.contexts.add(defaultContext + suffix);
}
for (String suffix : suffixes) {
addProfiles(this.contexts, defaultContext, profiles, suffix);
}
Collections.reverse(this.contexts);
CompositePropertySource composite = new CompositePropertySource("defaultContextConsul");
for (String propertySourceContext : this.contexts) {
try {
ParentConsulPropertySource propertySource = null;
if (this.properties.getFormat() != FILES) {
propertySource = create(propertySourceContext,parentDataKey);
}
if (propertySource != null) {
composite.addPropertySource(propertySource);
}
}
catch (Exception e) {
if (this.properties.isFailFast()) {
log.error(
"Fail fast is set and there was an error reading configuration from consul.");
ReflectionUtils.rethrowRuntimeException(e);
}
else {
log.warn("Unable to load consul config from "
+ propertySourceContext, e);
}
}
}
return composite;
}
return null;
}
private String getContext(String prefix, String context) {
if (StringUtils.isEmpty(prefix)) {
return context;
}
else {
return prefix + "/" + context;
}
}
private void addIndex(String propertySourceContext, Long consulIndex) {
this.contextIndex.put(propertySourceContext, consulIndex);
}
private ParentConsulPropertySource create(String context, String parentDataKey) {
ParentConsulPropertySource propertySource = new ParentConsulPropertySource(context,
this.consul, this.properties);
propertySource.init(parentDataKey);
addIndex(context, propertySource.getInitialIndex());
return propertySource;
}
private void addProfiles(List contexts, String baseContext,
List profiles, String suffix) {
for (String profile : profiles) {
contexts.add(baseContext + this.properties.getProfileSeparator() + profile
+ suffix);
}
}
}
package com.dh.consul.config;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.QueryParams;
import com.ecwid.consul.v1.Response;
import com.ecwid.consul.v1.kv.model.GetValue;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.cloud.consul.config.ConsulConfigProperties;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.util.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
import static org.springframework.cloud.consul.config.ConsulConfigProperties.Format.PROPERTIES;
import static org.springframework.cloud.consul.config.ConsulConfigProperties.Format.YAML;
/**
* 加载consul default-context节点的配置文件
* @author dinghua
* @date 2019/10/14
* @since v1.0.0
*/
public class ParentConsulPropertySource extends EnumerablePropertySource {
private final Map properties = new LinkedHashMap<>();
private String context;
private ConsulConfigProperties configProperties;
private Long initialIndex;
public ParentConsulPropertySource(String context, ConsulClient source,
ConsulConfigProperties configProperties) {
super(context, source);
this.context = context;
this.configProperties = configProperties;
}
public void init(String parentDataKey) {
if (!this.context.endsWith("/")) {
this.context = this.context + "/";
}
Response> response = this.source.getKVValues(this.context,
this.configProperties.getAclToken(), QueryParams.DEFAULT);
this.initialIndex = response.getConsulIndex();
final List values = response.getValue();
ConsulConfigProperties.Format format = this.configProperties.getFormat();
switch (format) {
case KEY_VALUE:
parsePropertiesInKeyValueFormat(values);
break;
case PROPERTIES:
case YAML:
parsePropertiesWithNonKeyValueFormat(values, format,parentDataKey);
}
}
public Long getInitialIndex() {
return this.initialIndex;
}
/**
* Parses the properties in key value style i.e., values are expected to be either a
* sub key or a constant.
* @param values values to parse
*/
protected void parsePropertiesInKeyValueFormat(List values) {
if (values == null) {
return;
}
for (GetValue getValue : values) {
String key = getValue.getKey();
if (!StringUtils.endsWithIgnoreCase(key, "/")) {
key = key.replace(this.context, "").replace('/', '.');
String value = getValue.getDecodedValue();
this.properties.put(key, value);
}
}
}
/**
* Parses the properties using the format which is not a key value style i.e., either
* java properties style or YAML style.
* @param values values to parse
* @param format format in which the values should be parsed
*/
protected void parsePropertiesWithNonKeyValueFormat(List values,
ConsulConfigProperties.Format format,String parentDataKey) {
if (values == null) {
return;
}
// 只读取default-context节点的配置
if(!this.context.contains(this.configProperties.getDefaultContext())){
return;
}
for (GetValue getValue : values) {
String key = getValue.getKey().replace(this.context, "");
if (parentDataKey.equals(key)) {
parseValue(getValue, format);
}
}
}
protected void parseValue(GetValue getValue, ConsulConfigProperties.Format format) {
String value = getValue.getDecodedValue();
if (value == null) {
return;
}
Properties props = generateProperties(value, format);
for (Map.Entry entry : props.entrySet()) {
this.properties.put(entry.getKey().toString(), entry.getValue());
}
}
protected Properties generateProperties(String value,
ConsulConfigProperties.Format format) {
final Properties props = new Properties();
if (format == PROPERTIES) {
try {
props.load(new ByteArrayInputStream(value.getBytes("ISO-8859-1")));
}
catch (IOException e) {
throw new IllegalArgumentException(
value + " can't be encoded using ISO-8859-1");
}
return props;
}
else if (format == YAML) {
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ByteArrayResource(value.getBytes()));
return yaml.getObject();
}
return props;
}
protected Map getProperties() {
return this.properties;
}
protected ConsulConfigProperties getConfigProperties() {
return this.configProperties;
}
protected String getContext() {
return this.context;
}
@Override
public Object getProperty(String name) {
return this.properties.get(name);
}
@Override
public String[] getPropertyNames() {
Set strings = this.properties.keySet();
return strings.toArray(new String[strings.size()]);
}
}
未完,待续