resttemplate 请求重试_RestTemplate实践(及遇到的问题)

在微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端。我们可以使用JDK原生的URLConnection、Apache的Http Client、Netty的异步HTTP Client, Spring的RestTemplate。但是,用起来最方便、最优雅的还是要属Feign了。这里介绍的是RestTemplate。

什么是RestTemplate?

RestTemplate是Spring提供的用于访问Rest服务的客户端,

RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,

可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。

ClientHttpRequestFactory接口主要提供了两种实现方式

1、一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接。

2、一种方式是使用HttpComponentsClientHttpRequestFactory方式,底层使用HttpClient访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息。

RestTemplate的核心之一 Http Client。

目前通过RestTemplate 的源码可知,RestTemplate 可支持多种 Http Client的http的访问,如下所示:

基于 JDK HttpURLConnection 的 SimpleClientHttpRequestFactory,默认。

基于 Apache HttpComponents Client 的 HttpComponentsClientHttpRequestFactory

基于 OkHttp3的OkHttpClientHttpRequestFactory。

基于 Netty4 的 Netty4ClientHttpRequestFactory。

其中HttpURLConnection 和 HttpClient 为原生的网络访问类,OkHttp3采用了 OkHttp3的框架,Netty4 采用了Netty框架。

xml配置的方式

请查看RestTemplate源码了解细节,知其然知其所以然!

RestTemplate默认是使用SimpleClientHttpRequestFactory,内部是调用jdk的HttpConnection,默认超时为-1

@Autowired

RestTemplate simpleRestTemplate;

@Autowired

RestTemplate restTemplate;

基于jdk的spring的RestTemplate

text/plain;charset=UTF-8

使用Httpclient连接池的方式

text/plain;charset=UTF-8

bean初始化+静态工具

线程安全的单例(懒汉模式)

基于jdk的spring的RestTemplate

importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.context.annotation.Lazy;importorg.springframework.http.client.SimpleClientHttpRequestFactory;importorg.springframework.http.converter.FormHttpMessageConverter;importorg.springframework.http.converter.HttpMessageConverter;importorg.springframework.http.converter.StringHttpMessageConverter;importorg.springframework.http.converter.json.MappingJackson2HttpMessageConverter;importorg.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;importorg.springframework.stereotype.Component;importorg.springframework.web.client.DefaultResponseErrorHandler;importorg.springframework.web.client.RestTemplate;importjavax.annotation.PostConstruct;importjava.nio.charset.Charset;importjava.util.ArrayList;importjava.util.List;@Component

@Lazy(false)public classSimpleRestClient {private static final Logger LOGGER = LoggerFactory.getLogger(SimpleRestClient.class);private staticRestTemplate restTemplate;static{

SimpleClientHttpRequestFactory requestFactory= newSimpleClientHttpRequestFactory();

requestFactory.setReadTimeout(5000);

requestFactory.setConnectTimeout(5000);//添加转换器

List> messageConverters = new ArrayList<>();

messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));

messageConverters.add(newFormHttpMessageConverter());

messageConverters.add(newMappingJackson2XmlHttpMessageConverter());

messageConverters.add(newMappingJackson2HttpMessageConverter());

restTemplate= newRestTemplate(messageConverters);

restTemplate.setRequestFactory(requestFactory);

restTemplate.setErrorHandler(newDefaultResponseErrorHandler());

LOGGER.info("SimpleRestClient初始化完成");

}privateSimpleRestClient() {

}

@PostConstructpublic staticRestTemplate getClient() {returnrestTemplate;

}

}

使用Httpclient连接池的方式

importorg.apache.http.Header;importorg.apache.http.client.HttpClient;importorg.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;importorg.apache.http.impl.client.DefaultHttpRequestRetryHandler;importorg.apache.http.impl.client.HttpClientBuilder;importorg.apache.http.impl.client.HttpClients;importorg.apache.http.impl.conn.PoolingHttpClientConnectionManager;importorg.apache.http.message.BasicHeader;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.context.annotation.Lazy;importorg.springframework.http.client.HttpComponentsClientHttpRequestFactory;importorg.springframework.http.converter.FormHttpMessageConverter;importorg.springframework.http.converter.HttpMessageConverter;importorg.springframework.http.converter.StringHttpMessageConverter;importorg.springframework.http.converter.json.MappingJackson2HttpMessageConverter;importorg.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;importorg.springframework.stereotype.Component;importorg.springframework.web.client.DefaultResponseErrorHandler;importorg.springframework.web.client.RestTemplate;importjavax.annotation.PostConstruct;importjava.nio.charset.Charset;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.TimeUnit;@Component

@Lazy(false)public classRestClient {private static final Logger LOGGER = LoggerFactory.getLogger(RestClient.class);private staticRestTemplate restTemplate;static{//长连接保持30秒

PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);//总连接数

pollingConnectionManager.setMaxTotal(1000);//同路由的并发数

pollingConnectionManager.setDefaultMaxPerRoute(1000);

HttpClientBuilder httpClientBuilder=HttpClients.custom();

httpClientBuilder.setConnectionManager(pollingConnectionManager);//重试次数,默认是3次,没有开启

httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(2, true));//保持长连接配置,需要在头添加Keep-Alive

httpClientBuilder.setKeepAliveStrategy(newDefaultConnectionKeepAliveStrategy());//RequestConfig.Builder builder = RequestConfig.custom();//builder.setConnectionRequestTimeout(200);//builder.setConnectTimeout(5000);//builder.setSocketTimeout(5000);//

//RequestConfig requestConfig = builder.build();//httpClientBuilder.setDefaultRequestConfig(requestConfig);

List headers = new ArrayList<>();

headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));

headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));

headers.add(new BasicHeader("Accept-Language", "zh-CN"));

headers.add(new BasicHeader("Connection", "Keep-Alive"));

httpClientBuilder.setDefaultHeaders(headers);

HttpClient httpClient=httpClientBuilder.build();//httpClient连接配置,底层是配置RequestConfig

HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = newHttpComponentsClientHttpRequestFactory(httpClient);// 连接超时

clientHttpRequestFactory.setConnectTimeout(5000);

// 数据读取超时时间,即SocketTimeout

clientHttpRequestFactory.setReadTimeout(5000);

// 连接不够用的等待时间,不宜过长,必须设置,比如连接不够用时,时间过长将是灾难性的

clientHttpRequestFactory.setConnectionRequestTimeout(200);//缓冲请求数据,默认值是true。通过POST或者PUT大量发送数据时,建议将此属性更改为false,以免耗尽内存。//clientHttpRequestFactory.setBufferRequestBody(false);//添加内容转换器

List> messageConverters = new ArrayList<>();

messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));

messageConverters.add(newFormHttpMessageConverter());

messageConverters.add(newMappingJackson2XmlHttpMessageConverter());

messageConverters.add(newMappingJackson2HttpMessageConverter());

restTemplate= newRestTemplate(messageConverters);

restTemplate.setRequestFactory(clientHttpRequestFactory);

restTemplate.setErrorHandler(newDefaultResponseErrorHandler());

LOGGER.info("RestClient初始化完成");

}privateRestClient() {

}

@PostConstructpublic staticRestTemplate getClient() {returnrestTemplate;

}

}

@Configurationpublic classRestConfig {

@BeanpublicRestTemplate restTemplate(){

RestTemplate restTemplate= newRestTemplate();returnrestTemplate;

}

@Bean("urlConnection")publicRestTemplate urlConnectionRestTemplate(){

RestTemplate restTemplate= new RestTemplate(newSimpleClientHttpRequestFactory());returnrestTemplate;

}

@Bean("httpClient")publicRestTemplate httpClientRestTemplate(){

RestTemplate restTemplate= new RestTemplate(newHttpComponentsClientHttpRequestFactory());returnrestTemplate;

}

@Bean("oKHttp3")publicRestTemplate OKHttp3RestTemplate(){

RestTemplate restTemplate= new RestTemplate(newOkHttp3ClientHttpRequestFactory());returnrestTemplate;

}

}

ErrorHolder

自定义的一个异常结果包装类

importorg.springframework.http.HttpHeaders;importorg.springframework.http.HttpStatus;importorg.springframework.web.client.HttpClientErrorException;importorg.springframework.web.client.HttpServerErrorException;

public classErrorHolder {privateHttpStatus statusCode;privateString statusText;privateString responseBody;privateHttpHeaders responseHeaders;publicErrorHolder(HttpStatus statusCode, String statusText, String responseBody) {this.statusCode =statusCode;this.statusText =statusText;this.responseBody =responseBody;

}publicErrorHolder(String statusText) {this.statusText =statusText;

}publicHttpStatus getStatusCode() {returnstatusCode;

}public voidsetStatusCode(HttpStatus statusCode) {this.statusCode =statusCode;

}publicString getStatusText() {returnstatusText;

}public voidsetStatusText(String statusText) {this.statusText =statusText;

}publicString getResponseBody() {returnresponseBody;

}public voidsetResponseBody(String responseBody) {this.responseBody =responseBody;

}publicHttpHeaders getResponseHeaders() {returnresponseHeaders;

}public voidsetResponseHeaders(HttpHeaders responseHeaders) {this.responseHeaders =responseHeaders;

}public staticErrorHolder build(Exception exception) {if (exception instanceofHttpServerErrorException) {

HttpServerErrorException e=(HttpServerErrorException) exception;return newErrorHolder(e.getStatusCode(), e.getStatusText(), e.getResponseBodyAsString());

}if (exception instanceofHttpClientErrorException) {

HttpClientErrorException e=(HttpClientErrorException) exception;return newErrorHolder(e.getStatusCode(), e.getStatusText(), e.getResponseBodyAsString());

}return newErrorHolder(exception.getMessage());

}

}

使用样例

api里面可以做自动的参数匹配:

如:http://you domainn name/test?empNo={empNo},则下面方法的最后一个参数为数据匹配参数,会自动根据key进行查找,然后替换

API没有声明异常,注意进行异常处理

更多使用语法请查看API文档

ResponseEntity> result = RestClient.getClient().exchange(DIVIDE_PLATE_API, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() {}, map("empNo", empNo));

List list =result.getBody();

ResponseEntity result = RestClient.getClient().exchange(DIVIDE_PLATE_API, HttpMethod.GET, HttpEntity.EMPTY, KyArea.class, map("empNo", empNo));

KyArea kyArea= result.getBody();

RestTemplate处理请求状态码为非200的返回数据

默认的 RestTemplate 有个机制是请求状态码非200 就抛出异常,会中断接下来的操作。如果不想中断对结果数据得解析,可以通过覆盖默认的 ResponseErrorHandler ,见下面的示例,示例中的方法中基本都是空方法,只要对hasError修改下,让他一直返回true,即是不检查状态码及抛异常了。

@Bean("sslRestTemplate")public RestTemplate getRestTemplate() throwsException {

RestTemplate sslRestTemplate= new RestTemplate(newHttpsClientRequestFactory());

ResponseErrorHandler responseErrorHandler= newResponseErrorHandler() {

@Overridepublic boolean hasError(ClientHttpResponse clientHttpResponse) throwsIOException {return true;

}

@Overridepublic void handleError(ClientHttpResponse clientHttpResponse) throwsIOException {

}

};

sslRestTemplate.setErrorHandler(responseErrorHandler);returnsslRestTemplate;

}

或者,修改resttemplate的源码,把对应的源码文件拷贝到自己的项目中,但不推荐。

RestTempate的访问的超时设置

例如,我用的是Httpclient的连接池,RestTemplate的超时设置依赖HttpClient的内部的三个超时时间设置。

HttpClient内部有三个超时时间设置:连接池获取可用连接超时,连接超时,读取数据超时:

1.setConnectionRequestTimeout从连接池中获取可用连接超时:设置从connect Manager获取Connection 超时时间,单位毫秒。

HttpClient中的要用连接时尝试从连接池中获取,若是在等待了一定的时间后还没有获取到可用连接(比如连接池中没有空闲连接了)则会抛出获取连接超时异常。

2.连接目标超时connectionTimeout,单位毫秒。

指的是连接目标url的连接超时时间,即客服端发送请求到与目标url建立起连接的最大时间。如果在该时间范围内还没有建立起连接,则就抛出connectionTimeOut异常。

如测试的时候,将url改为一个不存在的url:“

http://test.com” ,超时时间3000ms过后,系统报出异常:    org.apache.commons.httpclient.ConnectTimeoutException:The host did not accept the connection within timeout of 3000 ms 

3.等待响应超时(读取数据超时)socketTimeout ,单位毫秒。

连接上一个url后,获取response的返回等待时间 ,即在与目标url建立连接后,等待放回response的最大时间,在规定时间内没有返回响应的话就抛出SocketTimeout。

测试时,将socketTimeout 设置很短,会报等待响应超时。

我遇到的问题,restTemplate请求到一个高可用的服务是,返回的超时时间是设置值的2倍,是因为负载均衡器返回的重定向,导致httpClient底层认为没有超时,又请求一次,如果负载均衡器下有两个节点,就耗费connectionTimeout的双倍时间。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值