diff --git a/docs/src/main/asciidoc/spring-cloud-consul.adoc b/docs/src/main/asciidoc/spring-cloud-consul.adoc index e8d786d1..7abe180d 100644 --- a/docs/src/main/asciidoc/spring-cloud-consul.adoc +++ b/docs/src/main/asciidoc/spring-cloud-consul.adoc @@ -185,6 +185,21 @@ You could store a YAML document in any of the keys listed above. You can change the data key using `spring.cloud.consul.config.data-key`. +[[spring-cloud-consul-retry]] +== Consul Retry + +If you expect that the consul agent may occasionally be unavailable when +your app starts, you can ask it to keep trying after a failure. You need to add +`spring-retry` and `spring-boot-starter-aop` to your classpath. The default +behaviour is to retry 6 times with an initial backoff interval of 1000ms and an +exponential multiplier of 1.1 for subsequent backoffs. You can configure these +properties (and others) using `spring.cloud.consul.retry.*` configuration properties. +This works with both Spring Cloud Consul Config and Discovery registration. + +TIP: To take full control of the retry add a `@Bean` of type +`RetryOperationsInterceptor` with id "consulRetryInterceptor". Spring +Retry has a `RetryInterceptorBuilder` that makes it easy to create one. + [[spring-cloud-consul-bus]] == Spring Cloud Bus with Consul diff --git a/spring-cloud-consul-config/pom.xml b/spring-cloud-consul-config/pom.xml index a66beee2..8b678f04 100644 --- a/spring-cloud-consul-config/pom.xml +++ b/spring-cloud-consul-config/pom.xml @@ -22,6 +22,11 @@ spring-boot-starter-web true + + org.springframework.retry + spring-retry + true + com.ecwid.consul consul-api diff --git a/spring-cloud-consul-config/src/main/java/org/springframework/cloud/consul/config/ConsulPropertySourceLocator.java b/spring-cloud-consul-config/src/main/java/org/springframework/cloud/consul/config/ConsulPropertySourceLocator.java index 523484a3..93570c55 100644 --- a/spring-cloud-consul-config/src/main/java/org/springframework/cloud/consul/config/ConsulPropertySourceLocator.java +++ b/spring-cloud-consul-config/src/main/java/org/springframework/cloud/consul/config/ConsulPropertySourceLocator.java @@ -22,16 +22,19 @@ import java.util.Collections; import java.util.List; import org.springframework.cloud.bootstrap.config.PropertySourceLocator; +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 com.ecwid.consul.v1.ConsulClient; /** * @author Spencer Gibb */ +@Order(0) public class ConsulPropertySourceLocator implements PropertySourceLocator { private ConsulClient consul; @@ -44,6 +47,7 @@ public class ConsulPropertySourceLocator implements PropertySourceLocator { } @Override + @Retryable(interceptor = "consulRetryInterceptor") public PropertySource locate(Environment environment) { if (environment instanceof ConfigurableEnvironment) { ConfigurableEnvironment env = (ConfigurableEnvironment) environment; diff --git a/spring-cloud-consul-core/pom.xml b/spring-cloud-consul-core/pom.xml index 69232ed1..53b2c7a7 100644 --- a/spring-cloud-consul-core/pom.xml +++ b/spring-cloud-consul-core/pom.xml @@ -32,6 +32,16 @@ spring-cloud-commons true + + org.springframework.boot + spring-boot-starter-aop + true + + + org.springframework.retry + spring-retry + true + com.ecwid.consul consul-api diff --git a/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/ConsulAutoConfiguration.java b/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/ConsulAutoConfiguration.java index c6b5e414..d65e1b13 100644 --- a/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/ConsulAutoConfiguration.java +++ b/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/ConsulAutoConfiguration.java @@ -18,14 +18,21 @@ package org.springframework.cloud.consul; import com.ecwid.consul.v1.ConsulClient; +import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.retry.annotation.EnableRetry; +import org.springframework.retry.annotation.Retryable; +import org.springframework.retry.interceptor.RetryInterceptorBuilder; +import org.springframework.retry.interceptor.RetryOperationsInterceptor; /** * @author Spencer Gibb @@ -67,4 +74,23 @@ public class ConsulAutoConfiguration { 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(); + } + } } diff --git a/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/ConsulProperties.java b/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/ConsulProperties.java index 3015169b..9e397e77 100644 --- a/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/ConsulProperties.java +++ b/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/ConsulProperties.java @@ -28,12 +28,15 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("spring.cloud.consul") @Data public class ConsulProperties { + /** Consul agent hostname. Defaults to 'localhost'. */ @NotNull private String host = "localhost"; + /** Consul agent port. Defaults to '8500'. */ @NotNull private int port = 8500; + /** Is spring cloud consul enabled */ private boolean enabled = true; private String prefix = "config"; diff --git a/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/RetryProperties.java b/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/RetryProperties.java new file mode 100644 index 00000000..91888fb2 --- /dev/null +++ b/spring-cloud-consul-core/src/main/java/org/springframework/cloud/consul/RetryProperties.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.consul; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import lombok.Data; + +/** + * @author Spencer Gibb + */ +@ConfigurationProperties("spring.cloud.consul.retry") +@Data +public class RetryProperties { + + /** Initial retry interval in milliseconds. */ + private long initialInterval = 1000; + + /** Multiplier for next interval. */ + private double multiplier = 1.1; + + /** Maximum interval for backoff. */ + private long maxInterval = 2000; + + /** Maximum number of attempts. */ + private int maxAttempts = 6; +} diff --git a/spring-cloud-consul-dependencies/pom.xml b/spring-cloud-consul-dependencies/pom.xml index 8847ba71..e7721ee7 100644 --- a/spring-cloud-consul-dependencies/pom.xml +++ b/spring-cloud-consul-dependencies/pom.xml @@ -125,6 +125,11 @@ joda-time ${joda-time.version} + + org.springframework.retry + spring-retry + 1.1.2.RELEASE + org.springframework.cloud spring-cloud-netflix-dependencies diff --git a/spring-cloud-consul-discovery/pom.xml b/spring-cloud-consul-discovery/pom.xml index 7b076717..3b52ad4e 100644 --- a/spring-cloud-consul-discovery/pom.xml +++ b/spring-cloud-consul-discovery/pom.xml @@ -82,6 +82,11 @@ ribbon-loadbalancer true + + org.springframework.retry + spring-retry + true + org.projectlombok lombok diff --git a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulLifecycle.java b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulLifecycle.java index 408ac6d7..8ebc9f61 100644 --- a/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulLifecycle.java +++ b/spring-cloud-consul-discovery/src/main/java/org/springframework/cloud/consul/discovery/ConsulLifecycle.java @@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.discovery.AbstractDiscoveryLifecycle; +import org.springframework.retry.annotation.Retryable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -69,6 +70,12 @@ public class ConsulLifecycle extends AbstractDiscoveryLifecycle { service.setPort(port); } + @Override + @Retryable(interceptor = "consulRetryInterceptor") + public void start() { + super.start(); + } + @Override protected void register() { Assert.notNull(service.getPort(), "service.port has not been set");