-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Spring Retry For Failed Requests Using Feign #1457
Changes from 4 commits
88a36b3
fad2c5d
658f29e
8b4469d
14dd2d2
a91815a
5cd39df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,13 @@ | |
|
||
package org.springframework.cloud.netflix.feign; | ||
|
||
import com.netflix.hystrix.HystrixCommand; | ||
import feign.Contract; | ||
import feign.Feign; | ||
import feign.Retryer; | ||
import feign.codec.Decoder; | ||
import feign.codec.Encoder; | ||
import feign.hystrix.HystrixFeign; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
|
@@ -36,6 +43,8 @@ | |
import org.springframework.format.support.DefaultFormattingConversionService; | ||
import org.springframework.format.support.FormattingConversionService; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import com.netflix.hystrix.HystrixCommand; | ||
|
||
import feign.Contract; | ||
|
@@ -107,7 +116,7 @@ public Feign.Builder feignHystrixBuilder() { | |
@Scope("prototype") | ||
@ConditionalOnMissingBean | ||
public Feign.Builder feignBuilder() { | ||
return Feign.builder(); | ||
return Feign.builder().retryer(Retryer.NEVER_RETRY); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should go in a |
||
} | ||
|
||
@Bean | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,14 +16,15 @@ | |
|
||
package org.springframework.cloud.netflix.feign.ribbon; | ||
|
||
import java.util.Map; | ||
|
||
import com.netflix.client.config.IClientConfig; | ||
import com.netflix.loadbalancer.ILoadBalancer; | ||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; | ||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector; | ||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory; | ||
import org.springframework.retry.support.RetryTemplate; | ||
import org.springframework.util.ConcurrentReferenceHashMap; | ||
|
||
import com.netflix.client.config.IClientConfig; | ||
import com.netflix.loadbalancer.ILoadBalancer; | ||
import java.util.Map; | ||
|
||
/** | ||
* Factory for SpringLoadBalancer instances that caches the entries created. | ||
|
@@ -34,11 +35,16 @@ | |
public class CachingSpringLoadBalancerFactory { | ||
|
||
private final SpringClientFactory factory; | ||
private final RetryTemplate retryTemplate; | ||
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory; | ||
|
||
private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>(); | ||
|
||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory) { | ||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory, RetryTemplate retryTemplate, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should keep the old signature for backwards compatibility. |
||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { | ||
this.factory = factory; | ||
this.retryTemplate = retryTemplate; | ||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; | ||
} | ||
|
||
public FeignLoadBalancer create(String clientName) { | ||
|
@@ -48,7 +54,8 @@ public FeignLoadBalancer create(String clientName) { | |
IClientConfig config = this.factory.getClientConfig(clientName); | ||
ILoadBalancer lb = this.factory.getLoadBalancer(clientName); | ||
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class); | ||
FeignLoadBalancer client = new FeignLoadBalancer(lb, config, serverIntrospector); | ||
FeignLoadBalancer client = new FeignLoadBalancer(lb, config, serverIntrospector, retryTemplate, | ||
loadBalancedRetryPolicyFactory); | ||
this.cache.put(clientName, client); | ||
return client; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,54 +16,75 @@ | |
|
||
package org.springframework.cloud.netflix.feign.ribbon; | ||
|
||
import feign.Client; | ||
import feign.Request; | ||
import feign.Response; | ||
import feign.Util; | ||
|
||
import java.io.IOException; | ||
import java.net.URI; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.HashMap; | ||
import java.util.LinkedHashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.springframework.cloud.client.ServiceInstance; | ||
import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy; | ||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; | ||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; | ||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; | ||
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; | ||
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; | ||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector; | ||
|
||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.http.HttpRequest; | ||
import org.springframework.retry.RetryCallback; | ||
import org.springframework.retry.RetryContext; | ||
import org.springframework.retry.policy.NeverRetryPolicy; | ||
import org.springframework.retry.support.RetryTemplate; | ||
import com.netflix.client.AbstractLoadBalancerAwareClient; | ||
import com.netflix.client.ClientException; | ||
import com.netflix.client.ClientRequest; | ||
import com.netflix.client.DefaultLoadBalancerRetryHandler; | ||
import com.netflix.client.IResponse; | ||
import com.netflix.client.RequestSpecificRetryHandler; | ||
import com.netflix.client.RetryHandler; | ||
import com.netflix.client.config.CommonClientConfigKey; | ||
import com.netflix.client.config.IClientConfig; | ||
import com.netflix.loadbalancer.ILoadBalancer; | ||
import com.netflix.loadbalancer.Server; | ||
|
||
import feign.Client; | ||
import feign.Request; | ||
import feign.Response; | ||
import feign.Util; | ||
|
||
import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttpsIfNeeded; | ||
|
||
public class FeignLoadBalancer extends | ||
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> { | ||
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> implements | ||
ServiceInstanceChooser { | ||
|
||
private final int connectTimeout; | ||
private final int readTimeout; | ||
private final IClientConfig clientConfig; | ||
private final ServerIntrospector serverIntrospector; | ||
private final RetryTemplate retryTemplate; | ||
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory; | ||
|
||
public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, | ||
ServerIntrospector serverIntrospector) { | ||
ServerIntrospector serverIntrospector, RetryTemplate retryTemplate, | ||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { | ||
super(lb, clientConfig); | ||
this.setRetryHandler(RetryHandler.DEFAULT); | ||
this.retryTemplate = retryTemplate; | ||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; | ||
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig)); | ||
this.clientConfig = clientConfig; | ||
this.connectTimeout = clientConfig.get(CommonClientConfigKey.ConnectTimeout); | ||
this.readTimeout = clientConfig.get(CommonClientConfigKey.ReadTimeout); | ||
this.serverIntrospector = serverIntrospector; | ||
} | ||
|
||
@Override | ||
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) | ||
public RibbonResponse execute(final RibbonRequest request, IClientConfig configOverride) | ||
throws IOException { | ||
Request.Options options; | ||
final Request.Options options; | ||
if (configOverride != null) { | ||
options = new Request.Options( | ||
configOverride.get(CommonClientConfigKey.ConnectTimeout, | ||
|
@@ -74,26 +95,32 @@ public RibbonResponse execute(RibbonRequest request, IClientConfig configOverrid | |
else { | ||
options = new Request.Options(this.connectTimeout, this.readTimeout); | ||
} | ||
Response response = request.client().execute(request.toRequest(), options); | ||
return new RibbonResponse(request.getUri(), response); | ||
LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this); | ||
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like this should only be done once. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need the request when creating the policy There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still confused about where the request is used. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So you are saying that the policy should only be created once? The problem is we need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, the retry policy created below uses the request. Could there be a problem since the retry policy is scoped to the request and the template is a field? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just trying to make sure I understand the concern.... If multiple requests are executed simultaneously you are concerned about switching out the retry policy mid request? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's my worry There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, I guess the only good solution for now is to create a new |
||
: new InterceptorRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName())); | ||
return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() { | ||
@Override | ||
public RibbonResponse doWithRetry(RetryContext retryContext) throws IOException { | ||
Request feignRequest = null; | ||
if(retryContext instanceof LoadBalancedRetryContext) { | ||
ServiceInstance service = ((LoadBalancedRetryContext)retryContext).getServiceInstance(); | ||
if(service != null) { | ||
feignRequest = ((RibbonRequest)request.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest(); | ||
} | ||
} | ||
if(feignRequest == null) { | ||
feignRequest = request.toRequest(); | ||
} | ||
Response response = request.client().execute(feignRequest, options); | ||
return new RibbonResponse(request.getUri(), response); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler( | ||
RibbonRequest request, IClientConfig requestConfig) { | ||
if (this.clientConfig.get(CommonClientConfigKey.OkToRetryOnAllOperations, | ||
false)) { | ||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), | ||
requestConfig); | ||
} | ||
if (!request.toRequest().method().equals("GET")) { | ||
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), | ||
requestConfig); | ||
} | ||
else { | ||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), | ||
requestConfig); | ||
} | ||
return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), requestConfig); | ||
} | ||
|
||
@Override | ||
|
@@ -102,6 +129,12 @@ public URI reconstructURIWithServer(Server server, URI original) { | |
return super.reconstructURIWithServer(server, uri); | ||
} | ||
|
||
@Override | ||
public ServiceInstance choose(String serviceId) { | ||
return new RibbonLoadBalancerClient.RibbonServer(serviceId, | ||
this.getLoadBalancer().chooseServer(serviceId)); | ||
} | ||
|
||
static class RibbonRequest extends ClientRequest implements Cloneable { | ||
|
||
private final Request request; | ||
|
@@ -129,6 +162,33 @@ Client client() { | |
return this.client; | ||
} | ||
|
||
HttpRequest toHttpRequest() { | ||
return new HttpRequest() { | ||
@Override | ||
public HttpMethod getMethod() { | ||
return HttpMethod.resolve(RibbonRequest.this.toRequest().method()); | ||
} | ||
|
||
@Override | ||
public URI getURI() { | ||
return RibbonRequest.this.getUri(); | ||
} | ||
|
||
@Override | ||
public HttpHeaders getHeaders() { | ||
Map<String, List<String>> headers = new HashMap<String, List<String>>(); | ||
Map<String, Collection<String>> feignHeaders = RibbonRequest.this.toRequest().headers(); | ||
for(String key : feignHeaders.keySet()) { | ||
headers.put(key, new ArrayList<String>(feignHeaders.get(key))); | ||
} | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.putAll(headers); | ||
return httpHeaders; | ||
|
||
} | ||
}; | ||
} | ||
|
||
@Override | ||
public Object clone() { | ||
return new RibbonRequest(this.client, this.request, getUri()); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,8 +16,12 @@ | |
|
||
package org.springframework.cloud.netflix.ribbon.apache; | ||
|
||
import java.net.URI; | ||
|
||
import com.netflix.client.RequestSpecificRetryHandler; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to fix your ordering in a number of files ;-) |
||
import com.netflix.client.RetryHandler; | ||
import com.netflix.client.config.CommonClientConfigKey; | ||
import com.netflix.client.config.IClientConfig; | ||
import com.netflix.loadbalancer.ILoadBalancer; | ||
import com.netflix.loadbalancer.Server; | ||
import org.apache.http.HttpResponse; | ||
import org.apache.http.client.HttpClient; | ||
import org.apache.http.client.config.RequestConfig; | ||
|
@@ -27,10 +31,7 @@ | |
import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient; | ||
import org.springframework.web.util.UriComponentsBuilder; | ||
|
||
import com.netflix.client.config.CommonClientConfigKey; | ||
import com.netflix.client.config.IClientConfig; | ||
import com.netflix.loadbalancer.ILoadBalancer; | ||
import com.netflix.loadbalancer.Server; | ||
import java.net.URI; | ||
|
||
import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttpsIfNeeded; | ||
|
||
|
@@ -108,4 +109,9 @@ public URI reconstructURIWithServer(Server server, URI original) { | |
return super.reconstructURIWithServer(server, uri); | ||
} | ||
|
||
@Override | ||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) { | ||
return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,15 @@ | |
|
||
package org.springframework.cloud.netflix.zuul.filters; | ||
|
||
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.util.ClassUtils; | ||
import org.springframework.util.StringUtils; | ||
|
||
import javax.annotation.PostConstruct; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.LinkedHashMap; | ||
|
@@ -25,18 +34,6 @@ | |
import java.util.Map.Entry; | ||
import java.util.Set; | ||
|
||
import javax.annotation.PostConstruct; | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.util.ClassUtils; | ||
import org.springframework.util.StringUtils; | ||
|
||
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
import static com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE; | ||
|
||
/** | ||
|
@@ -71,7 +68,7 @@ public class ZuulProperties { | |
* Flag for whether retry is supported by default (assuming the routes themselves | ||
* support it). | ||
*/ | ||
private Boolean retryable; | ||
private Boolean retryable = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems unrelated. |
||
|
||
/** | ||
* Map of route names to properties. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems odd. Why a bunch of new imports?