Consul 配置中心代码阅读

393 阅读2分钟

1. 基本使用

引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-config</artifactId>
    </dependency>

定义自己的配置类

@ConfigurationProperties(prefix = "myconfig")
public class MyConfig {
    private String name;

    private Boolean enabled;
 }

Application注册

@EnableDiscoveryClient
@EnableConfigurationProperties({MyConfig.class})
@SpringBootApplication
public class AppApplication {
    public static void main(String[] args) {
        SpringApplication.run(AppApplication.class, args);
    }
}

使用

@RefreshScope //动态刷新
@RestController
public class MyController {

    @Autowired
    private MyConfig myConfig;

    @ResponseBody
    @RequestMapping("/config")
    public Object testConfig() {
        return myConfig;
    }
}

2. 工作原理

2.1 启动

先看配置类先看配置类ConsulConfigProperties,如下是可以看到引入了spring-cloud-starter-consul-config以后就会自动启用了consul配置中心,并初始化了一个定时任务ConfigWatch,用于做配置的更新。

@Configuration(proxyBeanMethods = false)
@ConditionalOnConsulEnabled
@ConditionalOnProperty(name = "spring.cloud.consul.config.enabled", matchIfMissing = true)
public class ConsulConfigAutoConfiguration {

	/**
	 * Name of the config watch task scheduler bean.
	 */
	public static final String CONFIG_WATCH_TASK_SCHEDULER_NAME = "configWatchTaskScheduler";

	@Configuration(proxyBeanMethods = false)
	@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,
				@Qualifier(CONFIG_WATCH_TASK_SCHEDULER_NAME) TaskScheduler taskScheduler) {
			return new ConfigWatch(properties, consul, locator.getContextIndexes(),
					taskScheduler);
		}

		@Bean(name = CONFIG_WATCH_TASK_SCHEDULER_NAME)
		@ConditionalOnProperty(name = "spring.cloud.consul.config.watch.enabled",
				matchIfMissing = true)
		public TaskScheduler configWatchTaskScheduler() {
			return new ThreadPoolTaskScheduler();
		}

	}

}

2.2 配置更新

ConfigWatch核心逻辑

  1. 继承了SmartLifecycle,会自动start,继承ApplicationEventPublisherAware获得应用publisher
  2. 在start中启动定时任务,默认1秒执行一次,可配置修改
  3. watchConfigKeyValues逻辑,对所有配置项
  • 调用consul接口请求配置数据
  • 如果有配置更新使用publisher发布RefreshEvent,下游进行数据更新
  • 缓存结果
public class ConfigWatch implements ApplicationEventPublisherAware, SmartLifecycle {
	//code removed

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
		this.publisher = publisher;
	}

	@Override
	public void start() {
		if (this.running.compareAndSet(false, true)) {
			this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
					this::watchConfigKeyValues, this.properties.getWatch().getDelay());
		}
	}
    
    @Timed("consul.watch-config-keys")
	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 (this.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 = this.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)) {
								log.trace("Context " + context + " has new index "
										+ newIndex);
								RefreshEventData data = new RefreshEventData(context,
										currentIndex, newIndex);
								this.publisher.publishEvent(
										new RefreshEvent(this, data, data.toString()));
							}
							
							this.consulIndexes.put(context, newIndex);
						}
					}

				}
				catch (Exception e) {
					//异常处理
				}
			}
		}
		this.firstTime = false;
	}
    

2.3 如何找到配置文件

参见org.springframework.cloud.consul.config.ConsulPropertySourceLocator

  1. 对每个配置的环境进行循环检查
  2. 构造配置文件路径,对一个key检查yml,yaml,properties几种
  3. 对每种路径调用consul API进行读取,读取到则进行解析并更新ConsulConfigProperties
@Retryable(interceptor = "consulRetryInterceptor")
	public PropertySource<?> locate(Environment environment) {
		if (environment instanceof ConfigurableEnvironment) {
			ConfigurableEnvironment env = (ConfigurableEnvironment) environment;

			String appName = this.properties.getName();

			if (appName == null) {
				appName = env.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 = getContext(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 = getContext(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, this.contextIndex);
					}
					if (propertySource != null) {
						composite.addPropertySource(propertySource);
					}
				}
				catch (Exception e) {
					//code removed
				}
			}

			return composite;
		}
		return null;
	}