快捷搜索:  汽车  科技

rest template例子(踩坑日记九)

rest template例子(踩坑日记九)经过和第三方厂商工程师沟通,配合一起压测,第三方结果如下:接着联系第三方工程师一起监控一下服务状态,疯狂找问题中。。。。loading。。。很容易就复现了这个问题,经过检查代码没有做很多的业务逻辑,但是平均响应时间和最大响应时间都很不理想。

背景

最近上线了一个功能,需要调用第三方的一个 API 获取相关业务数据,交互关系如下:

rest template例子(踩坑日记九)(1)

业务非常的单纯,开发时看到项目中采用的是 RestTemplate 做服务间的数据交互,那么这个功能也就继续沿用。

经过功能测试、发布上线。。。。

rest template例子(踩坑日记九)(2)

接着就陆陆续续收到了一些延迟预警,并且用户也反映客户端会偶尔出现卡顿现象。

疯狂找问题中。。。。

loading。。。

问题排查1、压测功能接口

rest template例子(踩坑日记九)(3)

很容易就复现了这个问题,经过检查代码没有做很多的业务逻辑,但是平均响应时间和最大响应时间都很不理想。

接着联系第三方工程师一起监控一下服务状态,

2、压测第三方接口

经过和第三方厂商工程师沟通,配合一起压测,第三方结果如下:

rest template例子(踩坑日记九)(4)

第三方API 非常稳定,30 ~ 50 毫秒就能返回结果。

3、排查服务器网络

肯定是服务间交互的网络问题,然后在服务器上直接搭建 JMeter 压测环境,得到结果和第三方提供的相差不大,稳定在 70 毫秒(约20毫秒的网络损耗)。

从这里基本定位到是服务调用方的问题了,进一步可以缩小到是 RestTemplate 。

问题分析

查看了 RestTemplate 实现原理发现初始化的时候用了 HttpComponentsClientHttpRequestFactory,目的是为了配置 RestTemplate 的超时配置。

源码如下:

rest template例子(踩坑日记九)(5)

从上图可以看到 RestTemplate 底层默认用的是 HttpClient,然后他的一个构建工厂HttpClientBuilder有一个属性是systemProperties。再看下面的源码:

rest template例子(踩坑日记九)(6)

如果 systemProperties 这个属性为 【true】的时候,HttpClient 保持长连接了,并且接下来会使用默认一个重用策略 DefaultClientConnectionReuseStrategy.INSTANCE;

rest template例子(踩坑日记九)(7)

并且初始化最大连接数是 5 ......

rest template例子(踩坑日记九)(8)

问题

使用默认的初始化 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的性能!

总结

在和第三方交互的时候要考虑到除业务之外细节,有条件的情况下一定要做压力测试,这样对自己的代码有一个更加清晰的认识。特别是用一些框架默认配置,要大致的看一下具体的实现方式,特别是需要知道是否需要自定义配置,自己的配置是否齐全。

猜您喜欢: