Consul配置中心扩展

consul介绍

Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等)。使用起来也较 为简单。Consul使用Go语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与Docker等轻量级容器可无缝配合 。

consul配置

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<PropertySourceLocator> propertySourceLocators = new ArrayList<>();

	@Override
	public int getOrder() {
		return this.order;
	}

	public void setPropertySourceLocators(
			Collection<PropertySourceLocator> 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<String> contexts = new ArrayList<>();

	private final LinkedHashMap<String, Long> contextIndex = new LinkedHashMap<>();

	public ParentConsulPropertySourceLocator(ConsulClient consul,
											 ConsulConfigProperties properties) {
		this.consul = consul;
		this.properties = properties;
	}

	public LinkedHashMap<String, Long> getContextIndexes() {
		return this.contextIndex;
	}

	@Override
	@Retryable(interceptor = "consulRetryInterceptor")
	public PropertySource<?> locate(Environment environment) {
		if (environment instanceof ConfigurableEnvironment) {
			ConfigurableEnvironment env = (ConfigurableEnvironment) environment;

			List<String> profiles = Arrays.asList(env.getActiveProfiles());

			String prefix = this.properties.getPrefix();

			List<String> 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<String> contexts, String baseContext,
			List<String> 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<ConsulClient> {

	private final Map<String, Object> 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<List<GetValue>> response = this.source.getKVValues(this.context,
				this.configProperties.getAclToken(), QueryParams.DEFAULT);

		this.initialIndex = response.getConsulIndex();

		final List<GetValue> 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<GetValue> 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<GetValue> 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<String, Object> 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<String> strings = this.properties.keySet();
		return strings.toArray(new String[strings.size()]);
	}

}

未完,待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Consul是一个开源工具,用于实现分布式系统的服务发现和配置管理。它由HashiCorp公司开发,使用Go语言编写,具有绿色、轻量级的特点。\[3\] 在使用Consul时,可以使用Consul-template来实现配置模板和Nginx配置的更新。首先,需要安装并运行Consul。可以从官网下载最新版本的Consul服务,并解压到指定目录。然后,将解压后的Consul可执行文件移动到/usr/local/bin目录下。可以使用consul --version命令来验证安装是否成功。\[1\] 接下来,可以启动Consul服务。使用consul agent命令来启动服务,并指定相关参数,如-server、-ui、-bootstrap-expect、-data-dir、-node、-client、-bind、-datacenter和-config-dir等。这些参数可以根据实际需求进行配置。\[2\] 一旦Consul服务启动成功,就可以开始使用Consul进行服务发现和配置管理了。Consul提供了一套API和命令行工具,可以用于注册和发现服务、配置管理等操作。可以通过编写Consul配置文件来定义服务和相关配置信息,并使用Consul-template来生成实际的Nginx配置文件。Consul-template使用HTTP长轮询来实现变更触发和配置更改,可以通过Consul的watch命令来实现。\[1\] 总结来说,Consul是一个分布式、高可用、可横向扩展的工具,用于实现分布式系统的服务发现和配置管理。通过安装和运行Consul,并结合Consul-template来实现Nginx配置的更新和重启功能。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* [Consul的介绍、安装与使用](https://blog.csdn.net/hudeyong926/article/details/121287659)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [consul配置和使用](https://blog.csdn.net/weixin_44105468/article/details/121450170)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值