rest template例子(踩坑日记九)
rest template例子(踩坑日记九)经过和第三方厂商工程师沟通,配合一起压测,第三方结果如下:接着联系第三方工程师一起监控一下服务状态,疯狂找问题中。。。。loading。。。很容易就复现了这个问题,经过检查代码没有做很多的业务逻辑,但是平均响应时间和最大响应时间都很不理想。
背景最近上线了一个功能,需要调用第三方的一个 API 获取相关业务数据,交互关系如下:
业务非常的单纯,开发时看到项目中采用的是 RestTemplate 做服务间的数据交互,那么这个功能也就继续沿用。
经过功能测试、发布上线。。。。
接着就陆陆续续收到了一些延迟预警,并且用户也反映客户端会偶尔出现卡顿现象。
疯狂找问题中。。。。
loading。。。
问题排查1、压测功能接口很容易就复现了这个问题,经过检查代码没有做很多的业务逻辑,但是平均响应时间和最大响应时间都很不理想。
接着联系第三方工程师一起监控一下服务状态,
2、压测第三方接口经过和第三方厂商工程师沟通,配合一起压测,第三方结果如下:
第三方API 非常稳定,30 ~ 50 毫秒就能返回结果。
3、排查服务器网络肯定是服务间交互的网络问题,然后在服务器上直接搭建 JMeter 压测环境,得到结果和第三方提供的相差不大,稳定在 70 毫秒(约20毫秒的网络损耗)。
从这里基本定位到是服务调用方的问题了,进一步可以缩小到是 RestTemplate 。
问题分析查看了 RestTemplate 实现原理发现初始化的时候用了 HttpComponentsClientHttpRequestFactory,目的是为了配置 RestTemplate 的超时配置。
源码如下:
从上图可以看到 RestTemplate 底层默认用的是 HttpClient,然后他的一个构建工厂HttpClientBuilder有一个属性是systemProperties。再看下面的源码:
如果 systemProperties 这个属性为 【true】的时候,HttpClient 保持长连接了,并且接下来会使用默认一个重用策略 DefaultClientConnectionReuseStrategy.INSTANCE;
并且初始化最大连接数是 5 ......
问题使用默认的初始化 HttpComponentsClientHttpRequestFactory 的时候,HttpClient 的长连接会生效,如果出现调用通的情况,长连接在超时时间内会一直保持着连接,并且会复用上一次的连接信息。
解决方案要解决这个问题也比较简单,只需要在初始化 HttpComponentsClientHttpRequestFactory 的时候,传入自定义的 HttpClient 来实例HttpComponentsClientHttpRequestFactory,这样就不会使用默认的HttpClient的长连接。
pom.xml 引入 httpclient 和 httpcore 依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.14</version>
</dependency>
增加 RestTemplateConfig.java
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
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.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(httpRequestFactory());
}
@Bean
public ClientHttpRequestFactory httpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
@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(800);
// 路由是对maxTotal的细分
connectionManager.setDefaultMaxPerRoute(100);
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(5000) // 返回数据的超时时间
.setConnectTimeout(3000) // 连接上服务器的超时时间
.setConnectionRequestTimeout(1000) // 从连接池中获取连接的超时时间
.build();
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.build();
}
}
代码中也一并配置了 HttpClient 最大连接数以及同路由并发数,增加高并发的时候 RestTemplate的性能!
总结在和第三方交互的时候要考虑到除业务之外细节,有条件的情况下一定要做压力测试,这样对自己的代码有一个更加清晰的认识。特别是用一些框架默认配置,要大致的看一下具体的实现方式,特别是需要知道是否需要自定义配置,自己的配置是否齐全。