Commit 882219f9 by saga Committed by Ryan Baxter

spring-retry listener features (#2609)

parent 8dfcdbed
......@@ -19,6 +19,7 @@ package org.springframework.cloud.netflix.feign.ribbon;
import java.util.Map;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
......@@ -34,12 +35,14 @@ import com.netflix.loadbalancer.ILoadBalancer;
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
* @author Gang Li
*/
public class CachingSpringLoadBalancerFactory {
private final SpringClientFactory factory;
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
private final LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory;
private final LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory;
private boolean enableRetry = false;
private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
......@@ -48,6 +51,7 @@ public class CachingSpringLoadBalancerFactory {
this.factory = factory;
this.loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(factory);
this.loadBalancedBackOffPolicyFactory = null;
this.loadBalancedRetryListenerFactory = null;
}
@Deprecated
......@@ -57,6 +61,7 @@ public class CachingSpringLoadBalancerFactory {
this.factory = factory;
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.loadBalancedBackOffPolicyFactory = null;
this.loadBalancedRetryListenerFactory = null;
}
@Deprecated
......@@ -67,14 +72,28 @@ public class CachingSpringLoadBalancerFactory {
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.enableRetry = enableRetry;
this.loadBalancedBackOffPolicyFactory = null;
this.loadBalancedRetryListenerFactory = null;
}
@Deprecated
//TODO remove in 2.0.0x
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
this.factory = factory;
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
this.loadBalancedRetryListenerFactory = null;
this.enableRetry = true;
}
public CachingSpringLoadBalancerFactory(SpringClientFactory factory, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
this.factory = factory;
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
this.loadBalancedRetryListenerFactory = loadBalancedRetryListenerFactory;
this.enableRetry = true;
}
......@@ -86,7 +105,7 @@ public class CachingSpringLoadBalancerFactory {
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
......
......@@ -22,6 +22,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.feign.FeignAutoConfiguration;
import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties;
......@@ -65,10 +66,11 @@ public class FeignRibbonClientAutoConfiguration {
@Primary
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory,
LoadBalancedRetryPolicyFactory retryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, loadBalancedBackOffPolicyFactory);
SpringClientFactory factory,
LoadBalancedRetryPolicyFactory retryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);
}
@Bean
......
......@@ -25,6 +25,7 @@ import java.io.IOException;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
......@@ -33,6 +34,7 @@ import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
......@@ -47,11 +49,13 @@ import com.netflix.loadbalancer.Server;
/**
* A {@link FeignLoadBalancer} that leverages Spring Retry to retry failed requests.
* @author Ryan Baxter
* @author Gang Li
*/
public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements ServiceInstanceChooser {
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
private final LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory;
private final LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory;
@Deprecated
//TODO remove in 2.0.x
......@@ -61,8 +65,11 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements Ser
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
this.loadBalancedBackOffPolicyFactory = new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
this.loadBalancedRetryListenerFactory = new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
}
@Deprecated
//TODO remove in 2.0.x
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
......@@ -70,7 +77,21 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements Ser
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory == null ?
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory() : loadBalancedBackOffPolicyFactory;
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory() : loadBalancedBackOffPolicyFactory;
this.loadBalancedRetryListenerFactory = new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
}
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
super(lb, clientConfig, serverIntrospector);
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory == null ?
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory() : loadBalancedBackOffPolicyFactory;
this.loadBalancedRetryListenerFactory = loadBalancedRetryListenerFactory == null ?
new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory() : loadBalancedRetryListenerFactory;
}
@Override
......@@ -91,6 +112,10 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements Ser
RetryTemplate retryTemplate = new RetryTemplate();
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
RetryListener[] retryListeners = this.loadBalancedRetryListenerFactory.createRetryListeners(this.getClientName());
if (retryListeners != null && retryListeners.length != 0) {
retryTemplate.setListeners(retryListeners);
}
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName()));
return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {
......
......@@ -35,8 +35,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
......@@ -112,6 +112,13 @@ public class RibbonAutoConfiguration {
}
@Bean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnMissingBean
public LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory() {
return new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
}
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
return new PropertiesFactory();
......
......@@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
......@@ -136,10 +137,9 @@ public class HttpClientRibbonConfiguration {
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
@ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate")
public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient(
IClientConfig config, ServerIntrospector serverIntrospector,
ILoadBalancer loadBalancer, RetryHandler retryHandler, CloseableHttpClient httpClient) {
RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(
httpClient, config, serverIntrospector);
IClientConfig config, ServerIntrospector serverIntrospector,
ILoadBalancer loadBalancer, RetryHandler retryHandler, CloseableHttpClient httpClient) {
RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(httpClient, config, serverIntrospector);
client.setLoadBalancer(loadBalancer);
client.setRetryHandler(retryHandler);
Monitors.registerObject("Client_" + this.name, client);
......@@ -150,13 +150,14 @@ public class HttpClientRibbonConfiguration {
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient(
IClientConfig config, ServerIntrospector serverIntrospector,
ILoadBalancer loadBalancer, RetryHandler retryHandler,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, CloseableHttpClient httpClient,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
IClientConfig config, ServerIntrospector serverIntrospector,
ILoadBalancer loadBalancer, RetryHandler retryHandler,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, CloseableHttpClient httpClient,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(
httpClient, config, serverIntrospector, loadBalancedRetryPolicyFactory,
loadBalancedBackOffPolicyFactory);
httpClient, config, serverIntrospector, loadBalancedRetryPolicyFactory,
loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);
client.setLoadBalancer(loadBalancer);
client.setRetryHandler(retryHandler);
Monitors.registerObject("Client_" + this.name, client);
......
......@@ -26,6 +26,7 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
......@@ -36,6 +37,7 @@ import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
......@@ -49,12 +51,15 @@ import com.netflix.loadbalancer.Server;
/**
* An Apache HTTP client which leverages Spring Retry to retry failed requests.
* @author Ryan Baxter
* @author Gang Li
*/
public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingHttpClient
implements ServiceInstanceChooser {
private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory =
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
private LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory =
new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
@Deprecated
//TODO remove in 2.0.x
......@@ -74,6 +79,8 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
}
@Deprecated
//TODO remove in 2.0.x
public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate,
IClientConfig config, ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
......@@ -83,6 +90,17 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
}
public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate,
IClientConfig config, ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
super(delegate, config, serverIntrospector);
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
this.loadBalancedRetryListenerFactory = loadBalancedRetryListenerFactory;
}
@Override
public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception {
final RequestConfig.Builder builder = RequestConfig.custom();
......@@ -136,6 +154,10 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingH
: new RetryPolicy(request, retryPolicy, this, this.getClientName()));
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
RetryListener[] retryListeners = this.loadBalancedRetryListenerFactory.createRetryListeners(this.getClientName());
if (retryListeners != null && retryListeners.length != 0) {
retryTemplate.setListeners(retryListeners);
}
return retryTemplate.execute(callback);
}
......
......@@ -35,6 +35,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
......@@ -58,8 +59,7 @@ public class OkHttpRibbonConfiguration {
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool(IClientConfig config,
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
public ConnectionPool httpClientConnectionPool(IClientConfig config, OkHttpClientConnectionPoolFactory connectionPoolFactory) {
Integer maxTotalConnections = config.getPropertyAsInteger(
CommonClientConfigKey.MaxTotalConnections,
DefaultClientConfigImpl.DEFAULT_MAX_TOTAL_CONNECTIONS);
......@@ -80,8 +80,7 @@ public class OkHttpRibbonConfiguration {
@Bean
@ConditionalOnMissingBean(OkHttpClient.class)
public OkHttpClient client(OkHttpClientFactory httpClientFactory,
ConnectionPool connectionPool, IClientConfig config) {
public OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, IClientConfig config) {
Boolean followRedirects = config.getPropertyAsBoolean(
CommonClientConfigKey.FollowRedirects,
DefaultClientConfigImpl.DEFAULT_FOLLOW_REDIRECTS);
......@@ -107,19 +106,20 @@ public class OkHttpRibbonConfiguration {
}
}
@Bean
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public RetryableOkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config,
ServerIntrospector serverIntrospector,
ILoadBalancer loadBalancer,
RetryHandler retryHandler,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
OkHttpClient delegate,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
public RetryableOkHttpLoadBalancingClient okHttpLoadBalancingClient(
IClientConfig config,
ServerIntrospector serverIntrospector,
ILoadBalancer loadBalancer,
RetryHandler retryHandler,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
OkHttpClient delegate,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient(delegate, config,
serverIntrospector, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory);
serverIntrospector, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);
client.setLoadBalancer(loadBalancer);
client.setRetryHandler(retryHandler);
Monitors.registerObject("Client_" + this.name, client);
......@@ -129,9 +129,10 @@ public class OkHttpRibbonConfiguration {
@Bean
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
@ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate")
public OkHttpLoadBalancingClient retryableOkHttpLoadBalancingClient(IClientConfig config,
ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer,
RetryHandler retryHandler, OkHttpClient delegate) {
public OkHttpLoadBalancingClient retryableOkHttpLoadBalancingClient(
IClientConfig config,
ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer,
RetryHandler retryHandler, OkHttpClient delegate) {
OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(delegate, config,
serverIntrospector);
client.setLoadBalancer(loadBalancer);
......
......@@ -24,6 +24,7 @@ import org.apache.commons.lang.BooleanUtils;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
......@@ -34,6 +35,7 @@ import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
......@@ -47,12 +49,15 @@ import com.netflix.loadbalancer.Server;
/**
* An OK HTTP client which leverages Spring Retry to retry failed request.
* @author Ryan Baxter
* @author Gang Li
*/
public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClient implements ServiceInstanceChooser {
private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory =
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
private LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory =
new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
@Deprecated
//TODO remove in 2.0.x
......@@ -62,6 +67,8 @@ public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClien
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
}
@Deprecated
//TODO remove in 2.0.x
public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
......@@ -70,12 +77,25 @@ public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClien
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
}
public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector,
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
super(delegate, config, serverIntrospector);
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
this.loadBalancedRetryListenerFactory = loadBalancedRetryListenerFactory;
}
private OkHttpRibbonResponse executeWithRetry(OkHttpRibbonRequest request, LoadBalancedRetryPolicy retryPolicy,
RetryCallback<OkHttpRibbonResponse, Exception> callback)
throws Exception {
RetryCallback<OkHttpRibbonResponse, Exception> callback) throws Exception {
RetryTemplate retryTemplate = new RetryTemplate();
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
RetryListener[] retryListeners = this.loadBalancedRetryListenerFactory.createRetryListeners(this.getClientName());
if (retryListeners != null && retryListeners.length != 0) {
retryTemplate.setListeners(retryListeners);
}
boolean retryable = request.getContext() == null ? true :
BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true);
retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy()
......
......@@ -23,6 +23,8 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
......@@ -42,6 +44,12 @@ public class CachingSpringLoadBalancerFactoryTests {
@Mock
private RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
@Mock
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory;
@Mock
private LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory;
private CachingSpringLoadBalancerFactory factory;
@Before
......@@ -78,4 +86,51 @@ public class CachingSpringLoadBalancerFactoryTests {
verify(this.delegate, times(1)).getClientConfig("client2");
}
@Test
public void delegateCreatesWithNoRetry() {
IClientConfig config = new DefaultClientConfigImpl();
config.set(CommonClientConfigKey.ConnectTimeout, 1000);
config.set(CommonClientConfigKey.ReadTimeout, 500);
when(this.delegate.getClientConfig("retry")).thenReturn(config);
CachingSpringLoadBalancerFactory factory = new CachingSpringLoadBalancerFactory(this.delegate);
FeignLoadBalancer client = this.factory.create("retry");
assertNotNull("client was null", client);
}
@Test
public void delegateCreatesWithRetry() {
IClientConfig config = new DefaultClientConfigImpl();
config.set(CommonClientConfigKey.ConnectTimeout, 1000);
config.set(CommonClientConfigKey.ReadTimeout, 500);
when(this.delegate.getClientConfig("retry")).thenReturn(config);
CachingSpringLoadBalancerFactory factory = new CachingSpringLoadBalancerFactory(
this.delegate, loadBalancedRetryPolicyFactory, false);
FeignLoadBalancer client = this.factory.create("retry");
assertNotNull("client was null", client);
}
@Test
public void delegateCreatesWithBackOff() {
IClientConfig config = new DefaultClientConfigImpl();
config.set(CommonClientConfigKey.ConnectTimeout, 1000);
config.set(CommonClientConfigKey.ReadTimeout, 500);
when(this.delegate.getClientConfig("retry")).thenReturn(config);
CachingSpringLoadBalancerFactory factory = new CachingSpringLoadBalancerFactory(
this.delegate, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory);
FeignLoadBalancer client = this.factory.create("retry");
assertNotNull("client was null", client);
}
@Test
public void delegateCreatesWithRetryListener() {
IClientConfig config = new DefaultClientConfigImpl();
config.set(CommonClientConfigKey.ConnectTimeout, 1000);
config.set(CommonClientConfigKey.ReadTimeout, 500);
when(this.delegate.getClientConfig("retry")).thenReturn(config);
CachingSpringLoadBalancerFactory factory = new CachingSpringLoadBalancerFactory(
this.delegate, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);
FeignLoadBalancer client = this.factory.create("retry");
assertNotNull("client was null", client);
}
}
......@@ -34,6 +34,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
......@@ -44,7 +45,10 @@ import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.http.HttpRequest;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.TerminatedRetryException;
import org.springframework.retry.backoff.BackOffContext;
import org.springframework.retry.backoff.BackOffInterruptedException;
import org.springframework.retry.backoff.BackOffPolicy;
......@@ -78,6 +82,7 @@ import static org.mockito.Mockito.when;
/**
* @author Ryan Baxter
* @author Gang Li
*/
public class RetryableFeignLoadBalancerTests {
@Mock
......@@ -287,6 +292,95 @@ public class RetryableFeignLoadBalancerTests {
}
@Test
public void retryListenerTest() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
IClientConfig config = mock(IClientConfig.class);
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(true).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
doReturn(defaultConnectTimeout).when(config).get(eq(CommonClientConfigKey.ConnectTimeout));
doReturn(defaultReadTimeout).when(config).get(eq(CommonClientConfigKey.ReadTimeout));
doReturn("").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://listener", new HashMap<String, Collection<String>>(),
new byte[]{}, StandardCharsets.UTF_8);
Client client = mock(Client.class);
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://listener"));
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
doThrow(new IOException("boom")).doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class));
MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory();
MyRetryListeners myRetryListeners = new MyRetryListeners();
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
backOffPolicyFactory, myRetryListeners);
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
assertEquals(200, ribbonResponse.toResponse().status());
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
assertEquals(1, backOffPolicyFactory.getCount());
assertEquals(1, myRetryListeners.getOnError());
}
@Test(expected = TerminatedRetryException.class)
public void retryListenerTestNoRetry() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
IClientConfig config = mock(IClientConfig.class);
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(true).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
doReturn(defaultConnectTimeout).when(config).get(eq(CommonClientConfigKey.ConnectTimeout));
doReturn(defaultReadTimeout).when(config).get(eq(CommonClientConfigKey.ReadTimeout));
doReturn("").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://listener", new HashMap<String, Collection<String>>(),
new byte[]{}, StandardCharsets.UTF_8);
Client client = mock(Client.class);
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://listener"));
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory();
MyRetryListenersNotRetry myRetryListenersNotRetry = new MyRetryListenersNotRetry();
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
backOffPolicyFactory, myRetryListenersNotRetry);
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
}
@Test
public void retryWithDefaultConstructorTest() throws Exception {
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
IClientConfig config = mock(IClientConfig.class);
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
doReturn(true).when(config).get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
doReturn(defaultConnectTimeout).when(config).get(eq(CommonClientConfigKey.ConnectTimeout));
doReturn(defaultReadTimeout).when(config).get(eq(CommonClientConfigKey.ReadTimeout));
doReturn("").when(config).getPropertyAsString(eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES),eq(""));
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
RibbonLoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
HttpRequest springRequest = mock(HttpRequest.class);
Request feignRequest = Request.create("GET", "http://listener", new HashMap<String, Collection<String>>(),
new byte[]{}, StandardCharsets.UTF_8);
Client client = mock(Client.class);
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(client, feignRequest, new URI("http://listener"));
Response response = Response.builder().status(200).headers(new HashMap<String, Collection<String>>()).build();
doThrow(new IOException("boom")).doReturn(response).when(client).execute(any(Request.class), any(Request.Options.class));
MyBackOffPolicyFactory backOffPolicyFactory = new MyBackOffPolicyFactory();
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config, inspector, loadBalancedRetryPolicyFactory,
backOffPolicyFactory);
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
assertEquals(200, ribbonResponse.toResponse().status());
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
assertEquals(1, backOffPolicyFactory.getCount());
}
class MyBackOffPolicyFactory implements LoadBalancedBackOffPolicyFactory, BackOffPolicy {
private int count = 0;
......@@ -311,4 +405,56 @@ public class RetryableFeignLoadBalancerTests {
}
}
class MyRetryListeners implements LoadBalancedRetryListenerFactory {
private int onError = 0;
@Override
public RetryListener[] createRetryListeners(String service) {
return new RetryListener[] {new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
onError++;
}
}};
}
public int getOnError() {
return onError;
}
}
class MyRetryListenersNotRetry implements LoadBalancedRetryListenerFactory {
@Override
public RetryListener[] createRetryListeners(String service) {
return new RetryListener[] {new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
return false;
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
}
}};
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment