1.简介

1.1 Consul is a tool for service discovery and configuration. Consul is distributed, highly available, and extremely scalable.

Consul provides several key features:

  • Service Discovery - Consul makes it simple for services to register themselves and to discover other services via a DNS or HTTP interface. External services such as SaaS providers can be registered as well.

  • Health Checking - Health Checking enables Consul to quickly alert operators about any issues in a cluster. The integration with service discovery prevents routing traffic to unhealthy hosts and enables service level circuit breakers.

  • Key/Value Storage - A flexible key/value store enables storing dynamic configuration, feature flagging, coordination, leader election and more. The simple HTTP API makes it easy to use anywhere.

  • Multi-Datacenter - Consul is built to be datacenter aware, and can support any number of regions without complex configuration.

Consul runs on Linux, Mac OS X, FreeBSD, Solaris, and Windows.

1.2 consul-api

Java client for Consul HTTP API (http://consul.io)

Supports all API endpoints (http://www.consul.io/docs/agent/http.html), all consistency modes and parameters (tags, datacenters etc.)

1.3 整体架构

2.源码分析

主要工程:

2.1 spring-cloud-consul-config

spring.factories

# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.consul.config.ConsulConfigAutoConfiguration # Bootstrap Configuration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration

2.1.1 ConsulConfigAutoConfiguration自动配置

@Configuration
@ConditionalOnConsulEnabled
@ConditionalOnProperty(name = "spring.cloud.consul.config.enabled", matchIfMissing = true)
public class ConsulConfigAutoConfiguration { @Configuration
@ConditionalOnClass(RefreshEndpoint.class)
protected static class ConsulRefreshConfiguration {
@Bean
@ConditionalOnProperty(name = "spring.cloud.consul.config.watch.enabled", matchIfMissing = true)
public ConfigWatch configWatch(ConsulConfigProperties properties,
ConsulPropertySourceLocator locator, ConsulClient consul) {
return new ConfigWatch(properties, consul, locator.getContextIndexes());
}
}
}

其中,ConfigWatch的主要代码如下:

    @Scheduled(fixedDelayString = "${spring.cloud.consul.config.watch.delay:1000}")
public void watchConfigKeyValues() {
if (this.running.get()) {
for (String context : this.consulIndexes.keySet()) { // turn the context into a Consul folder path (unless our config format are FILES)
if (properties.getFormat() != FILES && !context.endsWith("/")) {
context = context + "/";
} try {
Long currentIndex = this.consulIndexes.get(context);
if (currentIndex == null) {
currentIndex = -1L;
} // use the consul ACL token if found
String aclToken = properties.getAclToken();
if (StringUtils.isEmpty(aclToken)) {
aclToken = null;
} Response<List<GetValue>> response = this.consul.getKVValues(context, aclToken,
new QueryParams(this.properties.getWatch().getWaitTime(),
currentIndex)); // if response.value == null, response was a 404, otherwise it was a 200
// reducing churn if there wasn't anything
if (response.getValue() != null && !response.getValue().isEmpty()) {
Long newIndex = response.getConsulIndex(); if (newIndex != null && !newIndex.equals(currentIndex)) {
// don't publish the same index again, don't publish the first time (-1) so index can be primed
if (!this.consulIndexes.containsValue(newIndex) && !currentIndex.equals(-1L)) {
RefreshEventData data = new RefreshEventData(context, currentIndex, newIndex);
this.publisher.publishEvent(new RefreshEvent(this, data, data.toString()));
}
this.consulIndexes.put(context, newIndex);
}
} } catch (Exception e) {
// only fail fast on the initial query, otherwise just log the error
if (firstTime && this.properties.isFailFast()) {
log.error("Fail fast is set and there was an error reading configuration from consul.");
ReflectionUtils.rethrowRuntimeException(e);
} else if (log.isTraceEnabled()) {
log.trace("Error querying consul Key/Values for context '" + context + "'", e);
} else if (log.isWarnEnabled()) {
// simplified one line log message in the event of an agent failure
log.warn("Error querying consul Key/Values for context '" + context + "'. Message: " + e.getMessage());
}
}
}
}
firstTime = false;
}

2.1.2 启动配置ConsulConfigBootstrapConfiguration

定义:

@Configuration
@ConditionalOnConsulEnabled
public class ConsulConfigBootstrapConfiguration { @Configuration
@EnableConfigurationProperties
@Import(ConsulAutoConfiguration.class)
@ConditionalOnProperty(name = "spring.cloud.consul.config.enabled", matchIfMissing = true)
protected static class ConsulPropertySourceConfiguration {
@Autowired
private ConsulClient consul; @Bean
public ConsulConfigProperties consulConfigProperties() {
return new ConsulConfigProperties();
} @Bean
public ConsulPropertySourceLocator consulPropertySourceLocator(
ConsulConfigProperties consulConfigProperties) {
return new ConsulPropertySourceLocator(consul, consulConfigProperties);
}
}
}

其中,ConsulPropertySourceLocator主要流程

    @Override
@Retryable(interceptor = "consulRetryInterceptor")
public PropertySource<?> locate(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env); String appName = properties.getName(); if (appName == null) {
appName = propertyResolver.getProperty("spring.application.name");
} 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 = prefix + "/" + this.properties.getDefaultContext();
for (String suffix : suffixes) {
this.contexts.add(defaultContext + suffix);
}
for (String suffix : suffixes) {
addProfiles(this.contexts, defaultContext, profiles, suffix);
} String baseContext = prefix + "/" + appName;
for (String suffix : suffixes) {
this.contexts.add(baseContext + suffix);
}
for (String suffix : suffixes) {
addProfiles(this.contexts, baseContext, profiles, suffix);
} Collections.reverse(this.contexts); CompositePropertySource composite = new CompositePropertySource("consul"); for (String propertySourceContext : this.contexts) {
try {
ConsulPropertySource propertySource = null;
if (this.properties.getFormat() == FILES) {
Response<GetValue> response = this.consul.getKVValue(propertySourceContext, this.properties.getAclToken());
addIndex(propertySourceContext, response.getConsulIndex());
if (response.getValue() != null) {
ConsulFilesPropertySource filesPropertySource = new ConsulFilesPropertySource(propertySourceContext, this.consul, this.properties);
filesPropertySource.init(response.getValue());
propertySource = filesPropertySource;
}
} else {
propertySource = create(propertySourceContext, contextIndex);
}
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;
}

2.2 spring-cloud-consul-core

spring.factories

# Auto Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.consul.ConsulAutoConfiguration

实现如下:

@Configuration
@EnableConfigurationProperties
@ConditionalOnConsulEnabled
public class ConsulAutoConfiguration { @Bean
@ConditionalOnMissingBean
public ConsulProperties consulProperties() {
return new ConsulProperties();
} @Bean
@ConditionalOnMissingBean
public ConsulClient consulClient(ConsulProperties consulProperties) {
return new ConsulClient(consulProperties.getHost(), consulProperties.getPort());
} @Configuration
@ConditionalOnClass(Endpoint.class)
protected static class ConsulHealthConfig { @Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint("consul")
public ConsulEndpoint consulEndpoint(ConsulClient consulClient) {
return new ConsulEndpoint(consulClient);
} @Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledHealthIndicator("consul")
public ConsulHealthIndicator consulHealthIndicator(ConsulClient consulClient) {
return new ConsulHealthIndicator(consulClient);
}
} @ConditionalOnClass({ Retryable.class, Aspect.class, AopAutoConfiguration.class })
@Configuration
@EnableRetry(proxyTargetClass = true)
@Import(AopAutoConfiguration.class)
@EnableConfigurationProperties(RetryProperties.class)
protected static class RetryConfiguration { @Bean(name = "consulRetryInterceptor")
@ConditionalOnMissingBean(name = "consulRetryInterceptor")
public RetryOperationsInterceptor consulRetryInterceptor(
RetryProperties properties) {
return RetryInterceptorBuilder
.stateless()
.backOffOptions(properties.getInitialInterval(),
properties.getMultiplier(), properties.getMaxInterval())
.maxAttempts(properties.getMaxAttempts()).build();
}
}
}

定义ConsulProperties、consulClient、ConsulEndpoint、ConsulHealthIndicator、RetryOperationsInterceptor

2.3 spring-cloud-consul-discovery

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration,\
org.springframework.cloud.consul.discovery.configclient.ConsulConfigServerAutoConfiguration,\
org.springframework.cloud.consul.serviceregistry.ConsulAutoServiceRegistrationAutoConfiguration,\
org.springframework.cloud.consul.serviceregistry.ConsulServiceRegistryAutoConfiguration # Discovery Client Configuration
org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.consul.discovery.ConsulDiscoveryClientConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.consul.discovery.configclient.ConsulDiscoveryClientConfigServiceBootstrapConfiguration

2.3.1 ConsulRibbonClientConfiguration

@Configuration
@EnableConfigurationProperties
@ConditionalOnConsulEnabled
@ConditionalOnBean(SpringClientFactory.class)
@ConditionalOnProperty(value = "spring.cloud.consul.ribbon.enabled", matchIfMissing = true)
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = ConsulRibbonClientConfiguration.class)
public class RibbonConsulAutoConfiguration { }

2.3.2 ConsulConfigServerAutoConfiguration for config server

/**
* Extra configuration for config server if it happens to be registered with Consul.
*
* @author Dave Syer
*/
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass({ ConsulDiscoveryProperties.class, ConsulClient.class,
ConfigServerProperties.class })
public class ConsulConfigServerAutoConfiguration { @Autowired(required = false)
private ConsulDiscoveryProperties properties; @Autowired(required = false)
private ConfigServerProperties server; @PostConstruct
public void init() {
if (this.properties == null || this.server == null) {
return;
}
String prefix = this.server.getPrefix();
if (StringUtils.hasText(prefix)) {
this.properties.getTags().add("configPath="+prefix);
}
} }

2.3.3 ConsulAutoServiceRegistrationAutoConfiguration

@Configuration
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnMissingBean(type = "org.springframework.cloud.consul.discovery.ConsulLifecycle")
@ConditionalOnConsulEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter(ConsulServiceRegistryAutoConfiguration.class)
public class ConsulAutoServiceRegistrationAutoConfiguration { @Bean
@ConditionalOnMissingBean
public ConsulAutoServiceRegistration consulAutoServiceRegistration(ConsulServiceRegistry registry, ConsulDiscoveryProperties properties, ConsulAutoRegistration consulRegistration) {
return new ConsulAutoServiceRegistration(registry, properties, consulRegistration);
} @Bean
@ConditionalOnMissingBean
public ConsulAutoRegistration consulRegistration(ConsulDiscoveryProperties properties, ApplicationContext applicationContext,
ServletContext servletContext, HeartbeatProperties heartbeatProperties) {
return ConsulAutoRegistration.registration(properties, applicationContext, servletContext, heartbeatProperties);
} }

定义了ConsulAutoServiceRegistration、ConsulAutoRegistration

2.3.4 ConsulServiceRegistryAutoConfiguration

@Configuration
@ConditionalOnConsulEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.enabled", matchIfMissing = true)
@AutoConfigureBefore(ServiceRegistryAutoConfiguration.class)
public class ConsulServiceRegistryAutoConfiguration { @Autowired(required = false)
private TtlScheduler ttlScheduler; @Bean
@ConditionalOnMissingBean
public ConsulServiceRegistry consulServiceRegistry(ConsulClient consulClient, ConsulDiscoveryProperties properties,
HeartbeatProperties heartbeatProperties) {
return new ConsulServiceRegistry(consulClient, properties, ttlScheduler, heartbeatProperties);
} @Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("spring.cloud.consul.discovery.heartbeat.enabled")
public TtlScheduler ttlScheduler(ConsulClient consulClient, HeartbeatProperties heartbeatProperties) {
return new TtlScheduler(heartbeatProperties, consulClient);
} @Bean
@ConditionalOnMissingBean
public HeartbeatProperties heartbeatProperties() {
return new HeartbeatProperties();
} @Bean
@ConditionalOnMissingBean
public ConsulDiscoveryProperties consulDiscoveryProperties(InetUtils inetUtils) {
return new ConsulDiscoveryProperties(inetUtils);
} }

定义了 ConsulServiceRegistry、TtlScheduler、HeartbeatProperties、ConsulDiscoveryProperties

2.3.5 客户端发现ConsulDiscoveryClientConfiguration

@Configuration
@ConditionalOnConsulEnabled
@ConditionalOnProperty(value = "spring.cloud.consul.discovery.enabled", matchIfMissing = true)
@EnableConfigurationProperties
public class ConsulDiscoveryClientConfiguration { @Autowired
private ConsulClient consulClient; @Autowired(required = false)
private ServerProperties serverProperties; @Bean
@ConditionalOnMissingBean
@ConditionalOnProperty("spring.cloud.consul.discovery.heartbeat.enabled")
//TODO: move to service-registry for Edgware
public TtlScheduler ttlScheduler(HeartbeatProperties heartbeatProperties) {
return new TtlScheduler(heartbeatProperties, consulClient);
} @Bean
//TODO: move to service-registry for Edgware
public HeartbeatProperties heartbeatProperties() {
return new HeartbeatProperties();
} @Bean
//TODO: Split appropriate values to service-registry for Edgware
public ConsulDiscoveryProperties consulDiscoveryProperties(InetUtils inetUtils) {
return new ConsulDiscoveryProperties(inetUtils);
} @Bean
@ConditionalOnMissingBean
public ConsulDiscoveryClient consulDiscoveryClient(ConsulDiscoveryProperties discoveryProperties, final ApplicationContext context) {
ConsulDiscoveryClient discoveryClient = new ConsulDiscoveryClient(consulClient,
discoveryProperties, new LifecycleRegistrationResolver(context));
discoveryClient.setServerProperties(serverProperties); //null ok
return discoveryClient;
} class LifecycleRegistrationResolver implements ConsulDiscoveryClient.LocalResolver {
private ApplicationContext context; public LifecycleRegistrationResolver(ApplicationContext context) {
this.context = context;
} @Override
public String getInstanceId() {
ConsulRegistration registration = getBean(ConsulRegistration.class);
if (registration != null) {
return registration.getInstanceId();
}
ConsulLifecycle lifecycle = getBean(ConsulLifecycle.class);
if (lifecycle != null) {
return lifecycle.getInstanceId();
}
throw new IllegalStateException("Must have one of ConsulRegistration or ConsulLifecycle");
} @Override
public Integer getPort() {
ConsulRegistration registration = getBean(ConsulRegistration.class);
if (registration != null) {
return registration.getService().getPort();
}
ConsulLifecycle lifecycle = getBean(ConsulLifecycle.class);
if (lifecycle != null) {
return lifecycle.getConfiguredPort();
}
throw new IllegalStateException("Must have one of ConsulRegistration or ConsulLifecycle");
} <T> T getBean(Class<T> type) {
try {
return context.getBean(type);
} catch (NoSuchBeanDefinitionException e) {
}
return null;
}
} @Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "spring.cloud.consul.discovery.catalogServicesWatch.enabled", matchIfMissing = true)
public ConsulCatalogWatch consulCatalogWatch(
ConsulDiscoveryProperties discoveryProperties) {
return new ConsulCatalogWatch(discoveryProperties, consulClient);
}
}

2.3.6 ConsulDiscoveryClientConfigServiceBootstrapConfiguration

/**
* Helper for config client that wants to lookup the config server via discovery.
*
* @author Spencer Gibb
*/
@ConditionalOnClass(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration
@Import({ ConsulAutoConfiguration.class, ConsulDiscoveryClientConfiguration.class})
public class ConsulDiscoveryClientConfigServiceBootstrapConfiguration { @Bean
public ConsulDiscoveryProperties consulDiscoveryProperties(InetUtils inetUtils) {
ConsulDiscoveryProperties properties = new ConsulDiscoveryProperties(inetUtils);
// for bootstrap, lifecycle (and hence registration) is not needed, just discovery client
properties.getLifecycle().setEnabled(false);
return properties;
}
}

参考文献

【1】https://github.com/hashicorp/consul

【2】https://github.com/Ecwid/consul-api

【3】https://www.consul.io/api/index.html

spring cloud集成 consul源码分析的更多相关文章

  1. Spring Cloud 学习 之 Spring Cloud Eureka(源码分析)

    Spring Cloud 学习 之 Spring Cloud Eureka(源码分析) Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 ...

  2. spring boot 2.0 源码分析(四)

    在上一章的源码分析里,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文的,今天我们继续分析一下run函数接下来又做了那些事情.先把r ...

  3. spring boot 2.0 源码分析(一)

    在学习spring boot 2.0源码之前,我们先利用spring initializr快速地创建一个基本的简单的示例: 1.先从创建示例中的main函数开始读起: package com.exam ...

  4. Spring JPA实现逻辑源码分析总结

    1.SharedEntityManagerCreator: entitymanager的创建入口 该类被EntityManagerBeanDefinitionRegistrarPostProcesso ...

  5. Spring中Bean命名源码分析

    Spring中Bean命名源码分析 一.案例代码 首先是demo的整体结构 其次是各个部分的代码,代码本身比较简单,不是我们关注的重点 配置类 /** * @Author Helius * @Crea ...

  6. Spring Boot 自动配置 源码分析

    Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...

  7. Spring基础系列-AOP源码分析

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9560803.html 一.概述 Spring的两大特性:IOC和AOP. AOP是面向切 ...

  8. spring boot 2.0 源码分析(二)

    在上一章学习了spring boot 2.0启动的大概流程以后,今天我们来深挖一下SpringApplication实例变量的run函数. 先把这段run函数的代码贴出来: /** * Run the ...

  9. spring boot 2.0 源码分析(三)

    通过上一章的源码分析,我们知道了spring boot里面的listeners到底是什么(META-INF/spring.factories定义的资源的实例),以及它是创建和启动的,今天我们继续深入分 ...

随机推荐

  1. [Angular] Handle HTTP Errors in Angular with HttpErrorResponse interface

    When communicating with some backend API, data travels over the network using the HTTP protocol. As ...

  2. Oracle SGA具体解释

    SGA(SYSTEM Global Area )系统全局区 l 数据快速缓存 在Oracle进行数据处理的过程中,代价最昂贵的就是物理 I/O操作了.相同的数据从内存中得到要比从磁盘上读取快的多. 因 ...

  3. LeetCode_Maximum Depth of Binary Tree

    一.题目 Maximum Depth of Binary Tree My Submissions Given a binary tree, find its maximum depth. The ma ...

  4. Eclipse 更新Android SDK后,新建项目出现appcompat_v7project的相关问题

    Eclipse 更新Android SDK后,新建项目出现各种问题.网上各种解决方式,搞了好久,总结一下. 1.出现error: Error retrieving parent for item: N ...

  5. BZOJ 1264: [AHOI2006]基因匹配Match 树状数组+DP

    1264: [AHOI2006]基因匹配Match Description 基因匹配(match) 卡卡昨天晚上做梦梦见他和可可来到了另外一个星球,这个星球上生物的DNA序列由无数种碱基排列而成(地球 ...

  6. Elasticsearch之重要核心概念(cluster(集群)、shards(分配)、replicas(索引副本)、recovery(据恢复或叫数据重新分布)、gateway(es索引的持久化存储方式)、discovery.zen(es的自动发现节点机制机制)、Transport(内部节点或集群与客户端的交互方式)、settings(修改索引库默认配置)和mappings)

    Elasticsearch之重要核心概念如下: 1.cluster 代表一个集群,集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的.es的一个概念就是 ...

  7. TabPage判断重复添加Page

    ..... ........ ...........代码如下: bool isPag = true; foreach (TabPage page in tbpDynamicMenu.TabPages) ...

  8. 关于Spring的69个面试问答——终极列表 (转)

    这篇文章总结了一些关于Spring框架的重要问题,这些问题都是你在面试或笔试过程中可能会被问到的.下次你再也不用担心你的面试了,Java Code Geeks这就帮你解答. 大多数你可能被问到的问题都 ...

  9. hiho 1617 - 方格取数 - dp

    题目链接 描述 给定一个NxN的方格矩阵,每个格子中都有一个整数Aij.小Hi和小Ho各自选择一条从左上角格子到右下角格子的路径,要求路径中每一步只能向右或向下移动,并且两条路径不能相交(除了左上右下 ...

  10. SPOJ 1029 Matrix Summation【 二维树状数组 】

    题意:二维树状数组,更改值的时候有一点不一样, 是将a[x][y]设置为一个值,所以add的时候要将它和以前的值作差一下 #include<iostream> #include<cs ...