记一次诡异的生产问题解决思路。
问题描述:
我们的服务需要并发请求服务B的接口,采用定长线程池并发请求,相关工具类使用 RestTemplate ,生产上总是出现单次请求用时20秒以上的情况
排查过程:
1、检查服务器状况,发现cpu、内存使用率都很低,排除
2、检查服务器网络连接情况
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
ESTABLISHED: 已建立连接
CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭
TIME_WAIT:通信双方建立TCP连接后,主动关闭连接的一方就会进入TIME_WAIT状态
未发现异常。
3、抓包分析
sudo tcpdump host 【ip】 -w 【dir】/dump01.pcap
分析工具使用 Wireshark
发现目标接口接受到请求时就已经产生延迟了,那么问题不可能在接口方,只能是网络原因或者请求发出方,生产环境屹立n年不出问题,就不怀疑人家网络的问题了,从自身查起吧~
4、再一次检查服务器网络连接情况
虽然CLOSE_WAIT、TIME_WAIT数量没有异常,但突然发现ESTABLISHED的数量远低于并发线程数,按理来说达到并发最大值后网络连接数和并发最大数差不多,问题肯定在请求那块了,因为从现象来看,是请求排队了,发出了请求,但是对方过了n秒才收到
5、检查 RestTemplate 相关配置代码
发现没有设置最大连接数,扒了扒源码,发现默认最大连接数是10,怪不得,若并发50,那还有40个不得等连接空闲了才能轮到它吗?再一次印证了请求发出后对方延迟n秒才收到的现象。
问题解决:
配置下最大连接数就可以了
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create()
.setMaxConnTotal(100)
.setMaxConnPerRoute(50)
.build()); //解决问题的关键代码
return restTemplateBuilder
.additionalInterceptors(new RequestLogInterceptor()).detectRequestFactory(false).requestFactory(httpRequestFactory)
.build();
}
}
相关源码:
使用RestTemplateBuilder来配置RestTemplate bean时,会调用配置工厂类方法,此方法追踪restTemplateBuilder.build() 方法可以找到
private void configureRequestFactory(RestTemplate restTemplate) {
ClientHttpRequestFactory requestFactory = null;
if (this.requestFactory != null) {
requestFactory = this.requestFactory;
}
else if (this.detectRequestFactory) {
requestFactory = detectRequestFactory();
}
if (requestFactory != null) {
ClientHttpRequestFactory unwrappedRequestFactory = unwrapRequestFactoryIfNecessary(
requestFactory);
for (RequestFactoryCustomizer customizer : this.requestFactoryCustomizers) {
customizer.customize(unwrappedRequestFactory);
}
restTemplate.setRequestFactory(requestFactory);
}
}
如果你没显示指定RequestFactory类,那么它会调用detectRequestFactory()
private ClientHttpRequestFactory detectRequestFactory() {
for (Map.Entry<String, String> candidate : REQUEST_FACTORY_CANDIDATES
.entrySet()) {
ClassLoader classLoader = getClass().getClassLoader();
if (ClassUtils.isPresent(candidate.getKey(), classLoader)) {
Class<?> factoryClass = ClassUtils.resolveClassName(candidate.getValue(),
classLoader);
return (ClientHttpRequestFactory) BeanUtils.instantiate(factoryClass);
}
}
return new SimpleClientHttpRequestFactory();
}
这块代码作用就是看你引用的包里有没有 REQUEST_FACTORY_CANDIDATES 里设置的相关工厂类,有的话就默认使用了,没有的话就用SimpleClientHttpRequestFactory 了。
看下REQUEST_FACTORY_CANDIDATES里的东西都是啥:
private static final Map<String, String> REQUEST_FACTORY_CANDIDATES;
static {
Map<String, String> candidates = new LinkedHashMap<String, String>();
candidates.put("org.apache.http.client.HttpClient",
"org.springframework.http.client.HttpComponentsClientHttpRequestFactory");
candidates.put("okhttp3.OkHttpClient",
"org.springframework.http.client.OkHttp3ClientHttpRequestFactory");
candidates.put("com.squareup.okhttp.OkHttpClient",
"org.springframework.http.client.OkHttpClientHttpRequestFactory");
candidates.put("io.netty.channel.EventLoopGroup",
"org.springframework.http.client.Netty4ClientHttpRequestFactory");
REQUEST_FACTORY_CANDIDATES = Collections.unmodifiableMap(candidates);
}
在这个项目里是用org.springframework.http.client.HttpComponentsClientHttpRequestFactory 了
HttpComponentsClientHttpRequestFactory在构建对象时如果不传参,执行如下代码:
public HttpComponentsClientHttpRequestFactory() {
this.httpClient = HttpClients.createSystem();
}
public static CloseableHttpClient createSystem() {
return HttpClientBuilder.create().useSystemProperties().build();
}
可以看到,会使用HttpClientBuilder默认配置,进一步探查代码发现:
if (systemProperties) {
String s = System.getProperty("http.keepAlive", "true");
if ("true".equalsIgnoreCase(s)) {
s = System.getProperty("http.maxConnections", "5");
final int max = Integer.parseInt(s);
poolingmgr.setDefaultMaxPerRoute(max);
poolingmgr.setMaxTotal(2 * max);
}
}
默认最大连接数是10,所以我们改一下就可以了。
其底层是通过apache 的httpClient实现了连接池
参考:https://blog.csdn.net/qq_29738509/article/details/89962112
https://my.oschina.net/lifany/blog/688889