Moves ConfigWatch from @Scheduled to TaskScheduler

fixes gh-399
This commit is contained in:
Spencer Gibb
2018-06-05 14:19:03 -04:00
parent 4cefe09f09
commit 79d703a3db
4 changed files with 77 additions and 21 deletions

View File

@@ -218,10 +218,12 @@ spring:
The Consul Config Watch takes advantage of the ability of consul to https://www.consul.io/docs/agent/watches.html#keyprefix[watch a key prefix]. The Config Watch makes a blocking Consul HTTP API call to determine if any relevant configuration data has changed for the current application. If there is new configuration data a Refresh Event is published. This is equivalent to calling the `/refresh` actuator endpoint.
To change the frequency of when the Config Watch is called change `spring.cloud.consul.config.watch.delay`. The default value is 1000, which is in milliseconds.
To change the frequency of when the Config Watch is called change `spring.cloud.consul.config.watch.delay`. The default value is 1000, which is in milliseconds. The delay is the amount of time after the end of the previous invocation and the start of the next.
To disable the Config Watch set `spring.cloud.consul.config.watch.enabled=false`.
The watch uses a Spring `TaskScheduler` to schedule the call to consul. By default it is a `ThreadPoolTaskScheduler` with a `poolSize` of 1. To change the `TaskScheduler`, create a bean of type `TaskScheduler` named with the `ConsulConfigAutoConfiguration.CONFIG_WATCH_TASK_SCHEDULER_NAME` constant.
[[spring-cloud-consul-config-format]]
=== YAML or Properties with Config

View File

@@ -16,6 +16,12 @@
package org.springframework.cloud.consul.config;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.QueryParams;
import com.ecwid.consul.v1.Response;
@@ -23,41 +29,51 @@ import com.ecwid.consul.v1.kv.model.GetValue;
import io.micrometer.core.annotation.Timed;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.endpoint.event.RefreshEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.style.ToStringCreator;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.io.Closeable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.springframework.cloud.consul.config.ConsulConfigProperties.Format.FILES;
/**
* @author Spencer Gibb
*/
public class ConfigWatch implements Closeable, ApplicationEventPublisherAware {
public class ConfigWatch implements ApplicationEventPublisherAware, SmartLifecycle {
private static final Log log = LogFactory.getLog(ConfigWatch.class);
private final ConsulConfigProperties properties;
private final ConsulClient consul;
private LinkedHashMap<String, Long> consulIndexes;
private final TaskScheduler taskScheduler;
private final AtomicBoolean running = new AtomicBoolean(false);
private ApplicationEventPublisher publisher;
private boolean firstTime = true;
private ScheduledFuture<?> watchFuture;
public ConfigWatch(ConsulConfigProperties properties, ConsulClient consul, LinkedHashMap<String, Long> initialIndexes) {
this(properties, consul, initialIndexes, getTaskScheduler());
}
private static ThreadPoolTaskScheduler getTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.initialize();
return taskScheduler;
}
public ConfigWatch(ConsulConfigProperties properties, ConsulClient consul, LinkedHashMap<String, Long> initialIndexes,
TaskScheduler taskScheduler) {
this.properties = properties;
this.consul = consul;
this.consulIndexes = new LinkedHashMap<>(initialIndexes);
this.taskScheduler = taskScheduler;
}
@Override
@@ -65,12 +81,42 @@ public class ConfigWatch implements Closeable, ApplicationEventPublisherAware {
this.publisher = publisher;
}
@PostConstruct
@Override
public void start() {
this.running.compareAndSet(false, true);
if (this.running.compareAndSet(false, true)) {
this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(this::watchConfigKeyValues,
this.properties.getWatch().getDelay());
}
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
this.stop();
callback.run();
}
@Override
public int getPhase() {
return 0;
}
@Override
public void stop() {
if (this.running.compareAndSet(true, false) && this.watchFuture != null) {
this.watchFuture.cancel(true);
}
}
@Override
public boolean isRunning() {
return this.running.get();
}
@Scheduled(fixedDelayString = "${spring.cloud.consul.config.watch.delay:1000}")
@Timed(value ="consul.watch-config-keys")
public void watchConfigKeyValues() {
if (this.running.get()) {
@@ -138,11 +184,6 @@ public class ConfigWatch implements Closeable, ApplicationEventPublisherAware {
firstTime = false;
}
@Override
public void close() {
this.running.compareAndSet(true, false);
}
static class RefreshEventData {
private final String context;
private final Long prevIndex;

View File

@@ -18,12 +18,15 @@ package org.springframework.cloud.consul.config;
import com.ecwid.consul.v1.ConsulClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.consul.ConditionalOnConsulEnabled;
import org.springframework.cloud.endpoint.RefreshEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* @author Spencer Gibb
@@ -33,14 +36,23 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnProperty(name = "spring.cloud.consul.config.enabled", matchIfMissing = true)
public class ConsulConfigAutoConfiguration {
public static final String CONFIG_WATCH_TASK_SCHEDULER_NAME = "configWatchTaskScheduler";
@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());
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();
}
}
}

View File

@@ -35,6 +35,7 @@ import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -60,7 +61,7 @@ public class ConfigWatchTests {
setupWatch(eventPublisher, new GetValue(), "/app/", "2ee647bd-bd69-4118-9f34-b9a6e9e60746");
verify(eventPublisher, times(1)).publishEvent(any(RefreshEvent.class));
verify(eventPublisher, atLeastOnce()).publishEvent(any(RefreshEvent.class));
}
@Test