restTemplate请求重发的相关设置-通过配置
通过配置的方式
相关的pom文件需要引入:httpclient
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
根据响应的HTTP状态码重试
在RestTemplate中有一个服务不可用重试配置,可以通过
httpClientBuilder.setServiceUnavailableRetryStrategy来进行相关配置。通过查看源码可以发现,此处需要一个ServiceUnavailableRetryStrategy的实现,该接口已有一个默认的DefaultServiceUnavailableRetryStrategy配置。
默认的配置中,构造器代码如下:
public DefaultServiceUnavailableRetryStrategy(int maxRetries, int retryInterval) {
Args.positive(maxRetries, "Max retries");
Args.positive(retryInterval, "Retry interval");
this.maxRetries = maxRetries;
this.retryInterval = (long)retryInterval;
}
public DefaultServiceUnavailableRetryStrategy() {
this(1, 1000);
}
从默认的构造器源码中可以得到以下信息:
- 可以设置两个参数,一个是重试次数,一个是重试时间间隔。
- 默认的最大重试次数为1次,重试时间间隔为1s。
判断的相关代码如下:
public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) {
return executionCount <= this.maxRetries && response.getStatusLine().getStatusCode() == 503;
}
- 可以看出就是一个简单的判断,判断执行次数是否小于最大重试次数且返回的状态码是否为503.
如何定制自己需要的配置
- 次数和时间默认的配置已经给了构造方法。
- 如果想根据不同的状态则可以通过自己写一个类实现
ServiceUnavailableRetryStrategy
package com.doordiey.game.config;
import org.apache.http.HttpResponse;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args;
/**
* 根据返回的HTTP状态码决定是否要重试
*/
public class MyDefaultServiceUnavailableRetryStrategy implements ServiceUnavailableRetryStrategy {
private final int maxRetries;
private final long retryInterval;
public MyDefaultServiceUnavailableRetryStrategy(int maxRetries, int retryInterval) {
Args.positive(maxRetries, "Max retries");
Args.positive(retryInterval, "Retry interval");
this.maxRetries = maxRetries;
this.retryInterval = (long)retryInterval;
}
public MyDefaultServiceUnavailableRetryStrategy() {
this(1, 1000);
}
/**
* 修改此处的判断
* @param response
* @param executionCount
* @param context
* @return
*/
public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) {
return executionCount <= this.maxRetries && (response.getStatusLine().getStatusCode() == 503||
response.getStatusLine().getStatusCode() == 502)
;
}
public long getRetryInterval() {
return this.retryInterval;
}
}
配置类
package com.doordiey.game.config;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.apache.http.client.HttpClient;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
private static Logger logger = LoggerFactory.getLogger(RestTemplateConfig.class);
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(clientHttpRequestFactory());
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}
@Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
try {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
return true;
}
}).build();
httpClientBuilder.setSSLContext(sslContext);
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
hostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory).build();// 注册http和https请求
// 开始设置连接池
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
socketFactoryRegistry);
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
httpClientBuilder.setServiceUnavailableRetryStrategy(new MyDefaultServiceUnavailableRetryStrategy());
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)); // 重试次数
HttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient); // httpClient连接配置
return clientHttpRequestFactory;
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
logger.error("初始化HTTP连接池出错", e);
}
return null;
}
}
测试的接口
package com.doordiey.game.controller;
import com.doordiey.game.GameApplication;
import com.sun.corba.se.spi.ior.ObjectKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@RestController
public class GameController {
public static Logger logger = LoggerFactory.getLogger(GameApplication.class);
@Autowired
RestTemplate restTemplate;
@GetMapping("/play")
@ResponseStatus(code=HttpStatus.SERVICE_UNAVAILABLE,reason="server error")
public String play(){
logger.info("play");
return "ok";
}
@GetMapping("/test")
public void test(){
logger.info("test");
RestTemplate restTemplate = new RestTemplate();
String gg = restTemplate.getForObject("http://127.0.0.1:8086/play",String.class);
logger.info(gg);
}
}
请求错误重试
在RestTemplate中请求错误重试可以通过
httpClientBuilder.setRetryHandler来进行相关配置。通过查看源码可以发现,此处需要set一个HttpRequestRetryHandler的实现,已经提供了一个默认的DefaultHttpRequestRetryHandler。
默认的配置中,构造器代码如下:
protected DefaultHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled, Collection<Class<? extends IOException>> clazzes) {
this.retryCount = retryCount;
this.requestSentRetryEnabled = requestSentRetryEnabled;
this.nonRetriableClasses = new HashSet();
Iterator i$ = clazzes.iterator();
while(i$.hasNext()) {
Class<? extends IOException> clazz = (Class)i$.next();
this.nonRetriableClasses.add(clazz);
}
}
public DefaultHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled) {
this(retryCount, requestSentRetryEnabled, Arrays.asList(InterruptedIOException.class, UnknownHostException.class, ConnectException.class, SSLException.class));
}
public DefaultHttpRequestRetryHandler() {
this(3, false);
}
从默认的构造器源码中可以得到以下几个信息:
- 默认的重试次数为3次,且默认是不进行请求重发的。
- 对请求报错中的
InterruptedIOException.class, UnknownHostException.class, ConnectException.class, SSLException.class这四个错误类会特殊处理。
源码中主要进行是否重发的判断的是下面的代码:
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
Args.notNull(exception, "Exception parameter");
Args.notNull(context, "HTTP context");
if (executionCount > this.retryCount) {
return false;
} else if (this.nonRetriableClasses.contains(exception.getClass())) {
return false;
} else {
Iterator i$ = this.nonRetriableClasses.iterator();
Class rejectException;
do {
if (!i$.hasNext()) {
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpRequest request = clientContext.getRequest();
if (this.requestIsAborted(request)) {
return false;
}
if (this.handleAsIdempotent(request)) {
return true;
}
if (clientContext.isRequestSent() && !this.requestSentRetryEnabled) {
return false;
}
return true;
}
rejectException = (Class)i$.next();
} while(!rejectException.isInstance(exception));
return false;
}
}
从判断部分的源码可以得到以下信息:
- 先校验重试次数,再校验是否是那几个特殊的报错类,再进行其他校验。
综合以上得到的信息
- 可以通过设置重试次数,再非特殊报错的情况下,请求进行多次重试。
版权声明:本文为doordiev原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。