Spring Cloud - 在 RestTemplate 中重试工作? [英] Spring Cloud - Getting Retry Working In RestTemplate?
问题描述
我一直在将现有应用程序迁移到 Spring Cloud 的服务发现、Ribbon 负载平衡和断路器.该应用程序已经广泛使用了 RestTemplate,我已经能够成功使用模板的负载平衡版本.但是,我一直在测试有两个服务实例的情况,并且我将其中一个实例停止运行.我希望 RestTemplate 故障转移到下一个服务器.从我所做的研究来看,故障转移逻辑似乎存在于 Feign 客户端和使用 Zuul 时.LoadBalancedRest 模板似乎没有故障转移逻辑.在深入研究代码时,看起来 RibbonClientHttpRequestFactory 正在使用 netflix RestClient(它似乎具有进行重试的逻辑).
I have been migrating an existing application over to Spring Cloud's service discovery, Ribbon load balancing, and circuit breakers. The application already makes extensive use of the RestTemplate and I have been able to successfully use the load balanced version of the template. However, I have been testing the situation where there are two instances of a service and I drop one of those instances out of operation. I would like the RestTemplate to failover to the next server. From the research I have done, it appears that the fail-over logic exists in the Feign client and when using Zuul. It appears that the LoadBalancedRest template does not have logic for fail-over. In diving into the code, it looks like the RibbonClientHttpRequestFactory is using the netflix RestClient (which appears to have logic for doing retries).
那么我该从哪里开始工作呢?
So where do I go from here to get this working?
我不想使用 Feign 客户端,因为我将不得不扫描大量代码.我发现这个链接建议将 @Retryable 注释与 @HystrixCommand 一起使用,但这似乎应该是负载平衡休息模板的一部分.
I would prefer to not use the Feign client because I would have to sweep A LOT of code. I had found this link that suggested using the @Retryable annotation along with @HystrixCommand but this seems like something that should be a part of the load balanced rest template.
我深入研究了 RibbonClientHttpRequestFactory.RibbonHttpRequest 的代码:
I did some digging into the code for RibbonClientHttpRequestFactory.RibbonHttpRequest:
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
try {
addHeaders(headers);
if (outputStream != null) {
outputStream.close();
builder.entity(outputStream.toByteArray());
}
HttpRequest request = builder.build();
HttpResponse response = client.execute(request, config);
return new RibbonHttpResponse(response);
}
catch (Exception e) {
throw new IOException(e);
}
}
看来,如果我覆盖此方法并将其更改为使用client.executeWithLoadBalancer()",我可能能够利用 RestClient 中内置的重试逻辑?我想我可以创建自己的 RibbonClientHttpRequestFactory 版本来做到这一点?
It appears that if I override this method and change it to use "client.executeWithLoadBalancer()" that I might be able to leverage the retry logic that is built into the RestClient? I guess I could create my own version of the RibbonClientHttpRequestFactory to do this?
只是寻找有关最佳方法的指导.
Just looking for guidance on the best approach.
谢谢
推荐答案
回答我自己的问题:
在我进入细节之前,一个警示故事:
Before I get into the details, a cautionary tale:
在我的本地机器上测试故障转移时,Eureka 的自我保护模式让我陷入了困境.我建议在进行测试时关闭自我保护模式.因为我以常规速率删除节点然后重新启动(使用随机值使用不同的实例 ID),所以我绊倒了 Eureka 的自我保护模式.我最终在 Eureka 中找到了许多指向同一台机器、同一端口的实例.故障转移实际上是有效的,但选择的下一个节点碰巧是另一个死实例.一开始很困惑!
Eureka's self preservation mode sent me down a rabbit hole while testing the fail-over on my local machine. I recommend turning self preservation mode off while doing your testing. Because I was dropping nodes at a regular rate and then restarting (with a different instance ID using a random value), I tripped Eureka's self preservation mode. I ended up with many instances in Eureka that pointed to the same machine, same port. The fail-over was actually working but the next node that was chosen happened to be another dead instance. Very confusing at first!
我能够使用 RibbonClientHttpRequestFactory 的修改版本进行故障转移.因为 RibbonAutoConfiguration 使用这个工厂创建了一个负载均衡的 RestTemplate,而不是注入这个 rest 模板,我用我修改过的请求工厂版本创建了一个新的:
I was able to get fail-over working with a modified version of RibbonClientHttpRequestFactory. Because RibbonAutoConfiguration creates a load balanced RestTemplate with this factory, rather then injecting this rest template, I create a new one with my modified version of the request factory:
protected RestTemplate restTemplate;
@Autowired
public void customizeRestTemplate(SpringClientFactory springClientFactory, LoadBalancerClient loadBalancerClient) {
restTemplate = new RestTemplate();
// Use a modified version of the http request factory that leverages the load balacing in netflix's RestClient.
RibbonRetryHttpRequestFactory lFactory = new RibbonRetryHttpRequestFactory(springClientFactory, loadBalancerClient);
restTemplate.setRequestFactory(lFactory);
}
修改后的请求工厂只是 RibbonClientHttpRequestFactory 的副本,有两个小改动:
The modified Request Factory is just a copy of RibbonClientHttpRequestFactory with two minor changes:
1) 在 createRequest 中,我删除了从负载均衡器中选择服务器的代码,因为 RestClient 会为我们做这件事.2) 在内部类 RibbonHttpRequest 中,我将 executeInternal 更改为调用executeWithLoadBalancer".
1) In createRequest, I removed the code that was selecting a server from the load balancer because the RestClient will do that for us. 2) In the inner class, RibbonHttpRequest, I changed executeInternal to call "executeWithLoadBalancer".
全班:
@SuppressWarnings("deprecation")
public class RibbonRetryHttpRequestFactory implements ClientHttpRequestFactory {
private final SpringClientFactory clientFactory;
private LoadBalancerClient loadBalancer;
public RibbonRetryHttpRequestFactory(SpringClientFactory clientFactory, LoadBalancerClient loadBalancer) {
this.clientFactory = clientFactory;
this.loadBalancer = loadBalancer;
}
@Override
public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod) throws IOException {
String serviceId = originalUri.getHost();
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
RestClient client = clientFactory.getClient(serviceId, RestClient.class);
HttpRequest.Verb verb = HttpRequest.Verb.valueOf(httpMethod.name());
return new RibbonHttpRequest(originalUri, verb, client, clientConfig);
}
public class RibbonHttpRequest extends AbstractClientHttpRequest {
private HttpRequest.Builder builder;
private URI uri;
private HttpRequest.Verb verb;
private RestClient client;
private IClientConfig config;
private ByteArrayOutputStream outputStream = null;
public RibbonHttpRequest(URI uri, HttpRequest.Verb verb, RestClient client, IClientConfig config) {
this.uri = uri;
this.verb = verb;
this.client = client;
this.config = config;
this.builder = HttpRequest.newBuilder().uri(uri).verb(verb);
}
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(verb.name());
}
@Override
public URI getURI() {
return uri;
}
@Override
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
if (outputStream == null) {
outputStream = new ByteArrayOutputStream();
}
return outputStream;
}
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
try {
addHeaders(headers);
if (outputStream != null) {
outputStream.close();
builder.entity(outputStream.toByteArray());
}
HttpRequest request = builder.build();
HttpResponse response = client.executeWithLoadBalancer(request, config);
return new RibbonHttpResponse(response);
}
catch (Exception e) {
throw new IOException(e);
}
//TODO: fix stats, now that execute is not called
// use execute here so stats are collected
/*
return loadBalancer.execute(this.config.getClientName(), new LoadBalancerRequest<ClientHttpResponse>() {
@Override
public ClientHttpResponse apply(ServiceInstance instance) throws Exception {}
});
*/
}
private void addHeaders(HttpHeaders headers) {
for (String name : headers.keySet()) {
// apache http RequestContent pukes if there is a body and
// the dynamic headers are already present
if (!isDynamic(name) || outputStream == null) {
List<String> values = headers.get(name);
for (String value : values) {
builder.header(name, value);
}
}
}
}
private boolean isDynamic(String name) {
return name.equals("Content-Length") || name.equals("Transfer-Encoding");
}
}
public class RibbonHttpResponse extends AbstractClientHttpResponse {
private HttpResponse response;
private HttpHeaders httpHeaders;
public RibbonHttpResponse(HttpResponse response) {
this.response = response;
this.httpHeaders = new HttpHeaders();
List<Map.Entry<String, String>> headers = response.getHttpHeaders().getAllHeaders();
for (Map.Entry<String, String> header : headers) {
this.httpHeaders.add(header.getKey(), header.getValue());
}
}
@Override
public InputStream getBody() throws IOException {
return response.getInputStream();
}
@Override
public HttpHeaders getHeaders() {
return this.httpHeaders;
}
@Override
public int getRawStatusCode() throws IOException {
return response.getStatus();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.valueOf(response.getStatus()).name();
}
@Override
public void close() {
response.close();
}
}
}
这篇关于Spring Cloud - 在 RestTemplate 中重试工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!