RestTemplate 设置服务不可用重试策略
RestTemplate
一开始是在 Spring boot 的消费一个 rest 风格的 web 服务 遇到的,当时比较奇怪,正常来说与 http 请求相关的类名应该都带点 http 元素吧。
服务远程调用指南(RestTemplate、HttpClient) 中概述里有这样一句:
java开发中,使用 http 连接,访问第三方网络接口,通常使用的连接工具为 RestTemplate、HttpClient 和 OKHttp。
即已经存在了一个叫做 HttpClient 的包。RestTemplate 属于 Spring Web 中 client 下的一个实现类,官网的引导里也只需要引入 Spring Web 依赖就能使用。
因为项目不同的系统部分是独立的,数据也没有互通,所以需要在不同的系统间使用接口交互关键数据。一开始还好,但项目部署到正式站之后,数据量大和网络不稳定,导致时长丢失关键数据,进而导致一些业务数据状态异常。这时想起 PHP curl(类似的创建 http client 发送请求的工具)是可以设置重试次数,即当目标服务不稳定时可以自动重新发送请求。
搜索 RestTeplate 重试,找到了两种解决方案:
因为需要的依赖 HttpClient 已经安装好了,所以优先选择下面的方案,通过修改 RestTemplateConfig 配置文件,即修改 ClientHttpRequestFactory 相关配置来实现重试策略。
引入依赖 HttpClient
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
4.x 版本可以在 jdk 1.8 下使用,最新的 httpclient5 不确定兼容性。
修改配置类
基础配置
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(150 * 1000); // ms
factory.setConnectTimeout(150 * 1000); // ms
return factory;
}
}
基础配置通过 SimpleClientHttpRequestFactory 可以设置一些基本的读写超时、连接超时。这对于设置重试策略是不够的。
进阶配置
/**
* 定义 Http 请求拦截器
* @return
*/
@Bean
public ClientHttpRequestInterceptor interceptor() {
return new RestTemplateInterceptor();
}
/**
* 定义 HttpClient
* @return
*/
@Bean
public HttpClient httpClient() {
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
connectionManager.setMaxTotal(maxTotal); // 最大连接数
connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute); //单个路由最大连接数
connectionManager.setValidateAfterInactivity(validateAfterInactivity); // 最大空间时间
RequestConfig requestConfig = RequestConfig.custom()
//服务器返回数据(response)的时间,超过抛出read timeout
.setSocketTimeout(socketTimeout)
//连接上服务器(握手成功)的时间,超出抛出connect timeout
.setConnectTimeout(connectTimeout)
//从连接池中获取连接的超时时间,超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
.setConnectionRequestTimeout(connectionRequestTimeout)
.build();
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
// 设置重试策略和重试次数
.setServiceUnavailableRetryStrategy(new MyDefaultServiceUnavailableRetryStrategy())
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
.build();
}
/**
* 定义 请求工厂
* @return
*/
@Bean
public ClientHttpRequestFactory httpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
/**
* 定义 rest 模板
* @return
*/
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
restTemplate.setInterceptors(Collections.singletonList(interceptor()));
return restTemplate;
}
重新策略核心:
return HttpClientBuilder.create()
...
// 设置重试策略和重试次数
.setServiceUnavailableRetryStrategy(new MyDefaultServiceUnavailableRetryStrategy())
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
.build();
MyDefaultServiceUnavailableRetryStrategy
/**
* 根据返回的HTTP状态码决定是否要重试
*/
@Slf4j
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) {
log.info("retryRequest: " + response.getStatusLine().getStatusCode());
return executionCount <= this.maxRetries && (response.getStatusLine().getStatusCode() == 503||
response.getStatusLine().getStatusCode() == 502||
response.getStatusLine().getStatusCode() == 403||
response.getStatusLine().getStatusCode() == 405)
;
}
public long getRetryInterval() {
return this.retryInterval;
}
}
DefaultHttpRequestRetryHandler 两个参数 retryCount, requestSentRetryEnabled 分别表示重试次数和已发送请求重试开关。
到这里就可以了,会在请求返回状态码为 503、502、403、405 会发起请求重试,重试一次,间隔 1s。
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。