请求发出后延迟n秒对方才收到的情况问题解决

记一次诡异的生产问题解决思路。

问题描述:

我们的服务需要并发请求服务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

https://www.cnblogs.com/sword-successful/p/13505610.html

https://www.jianshu.com/p/90ec27b3b518

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值