相关文章推荐
怕考试的打火机  ·  pandas: DataFrame ...·  9 月前    · 
没有腹肌的围巾  ·  MySQL ...·  1 年前    · 
乐观的西瓜  ·  使用 MAUI 在 Windows 和 ...·  1 年前    · 

问题描述:

项目场景:实际项目创建一个固定数量的线程池,用来消费用户数据,但偶尔会发现有些线程消失了,也就是线程不进行工作,实际工作的线程数量一直减少,直至彻底不消费用户数据。

Executor executor = Executors.newFixedThreadPool(100);

原因分析:

首先排除代码的逻辑错误,确保没有出现死锁等情况。在确保没有死锁的情况下,通过Thread Dump得到日志进一步分析。

"pool-6-thread-29" #118 prio=5 os_prio=0 tid=0x00007ff9dc006800 nid=0x6ee0 runnable [0x00007ff9c59e6000]
   java.lang.Thread.State: RUNNABLE
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.read(SocketInputStream.java:150)
	at java.net.SocketInputStream.read(SocketInputStream.java:121)
	at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
	at sun.security.ssl.InputRecord.readV3Record(InputRecord.java:593)
	at sun.security.ssl.InputRecord.read(InputRecord.java:532)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:954)
	- locked <0x00000006d298e328> (a java.lang.Object)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1343)
	- locked <0x00000006d298e338> (a java.lang.Object)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1371)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1355)
	at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:396)
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.upgrade(DefaultHttpClientConnectionOperator.java:193)
	at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.upgrade(PoolingHttpClientConnectionManager.java:389)
	at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:416)
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:237)
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:111)
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
	at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:89)
	at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
	at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:724)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:681)
	at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:597)
	....

通过任务对应得到“消失的线程”,得到线程的name,拿到线程对应具体状态RUNNABLE。
在java的文档里对RUNNABLE的描述是这样的:

A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.
处于 runnable 状态下的线程正在 Java 虚拟机中执行,但它可能正在等待来自于操作系统的其它资源,比如处理器。

根据堆栈信息,可以看到具体出问题的是使用RestTemplate请求的时候出现问题,有可能是一直等待网络资源。
出现问题的RestTemplate由于业务原因,没有使用默认的SimpleClientHttpRequestFactory,而是使用了HttpClient作为RequestFactory,这里拓展一下,RestTemplate可以设置不同的RequestFactory,通过源码查看,内置已经支持如下:
在这里插入图片描述通过源码直接Debugger到java.net.SocketInputStream,会发现加了代理的请求,请求的SocketTimeout为0,这是最直接的原因,导致了线程一直在登录服务端的返回,彻底阻塞,影响后续任务。
在这里插入图片描述但项目实际设置了ConnectTimeout和SocketTimeout,而且在非代理请求情况,这里显示的是设置的SocketTimeout值,但加了代理后,就出现了0,推测是加了代理,HTTPClient内部的一些问题。

 RequestConfig config = RequestConfig.custom()
 .setConnectTimeout(httpConfig.getConnectTimeout())
 .setSocketTimeout(httpConfig.getReadTimeout())
 .setProxy(httpConfig.getProxy())
 .setRedirectsEnabled(httpConfig.isRedirect()).build();

源码分析:

https请求过程中,加代理和不加代理的主要区别在于创建路由过程不一样。 创建路由方法位于:org.apache.http.impl.execchain.MainClientExec#establishRoute

圈红框部分的代码,就是代理为了创建代理路由需要额外运行的代码,重点在:

 case HttpRouteDirector.CONNECT_PROXY:
                this.connManager.connect(
                        managedConn,
                        route,
                        timeout > 0 ? timeout : 0,
                        context);
                final HttpHost proxy  = route.getProxyHost();
                tracker.connectProxy(proxy, false);
                break;

其中在创建SocketConfig的过程中,代码如下:
org.apache.http.impl.conn.PoolingHttpClientConnectionManager#resolveSocketConfig

private SocketConfig resolveSocketConfig(final HttpHost host) { SocketConfig socketConfig = this.configData.getSocketConfig(host); if (socketConfig == null) { socketConfig = this.configData.getDefaultSocketConfig(); if (socketConfig == null) { socketConfig = SocketConfig.DEFAULT; return socketConfig;

该方法默认中,socketConfig 、this.configData.getDefaultSocketConfig();都为空,所以代理路由Route使用了默认的SocketConfig.DEFAULT。
查看org.apache.http.config.SocketConfig的默认对象:
在这里插入图片描述此处默认的SocketTimeout为0,没有超时,一直阻塞。
到此为止,定位到了具体的错误根源。

根据资料查找
1、httpclient 4.3.6之前的一个bug,会导致连接一直阻塞,导致不会超时,但检查项目httpclient版本高于4.3.6。
2、官方已经提示jdk8的某些版本存在该bug,但新版本已经修复,本次出现问题的jdk版本为1.8.0_25和1.8.0_191,处于未修复的版本,需要自己项目代码进行修复。
[JDK-8075484] SocketInputStream.socketRead0 can hang even with soTimeout set - Java Bug System
https://bugs.openjdk.java.net/browse/JDK-8075484#

解决方案:

private SocketConfig resolveSocketConfig(final HttpHost host) { SocketConfig socketConfig = this.configData.getSocketConfig(host); if (socketConfig == null) { socketConfig = this.configData.getDefaultSocketConfig(); if (socketConfig == null) { socketConfig = SocketConfig.DEFAULT; return socketConfig;

从上面的 this.configData.getDefaultSocketConfig();入手,设置DefaultSocketConfig();

RequestConfig config = RequestConfig.custom() .setConnectTimeout(httpConfig.getConnectTimeout()) .setSocketTimeout(httpConfig.getReadTimeout()) .setProxy(httpConfig.getProxy()) .setRedirectsEnabled(httpConfig.isRedirect()).build(); SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( getSSLContext(), new String[]{"TLSv1"}, null, NoopHostnameVerifier.INSTANCE); CloseableHttpClient httpClient = HttpClients.custom() .setDefaultRequestConfig(config).setSSLSocketFactory(sslSocketFactory) .setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36") //这里修改默认配置里的的SocketTimeout为40秒 .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(40000).build()).build(); RestTemplate restTemplate = new RestTemplate(); restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));

重点就是加了一行代码:.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(40000).build())

问题描述:项目场景:实际项目创建一个固定数量的线程池,用来消费用户数据,但偶尔会发现有些线程消失了,也就是线程不进行工作,实际工作的线程数量一直减少,直至彻底不消费用户数据。Executor executor = Executors.newFixedThreadPool(100);原因分析:首先排除代码的逻辑错误,确保没有出现死锁等情况。在确保没有死锁的情况下,通过Thread Dump得到日志进一步分析。"pool-6-thread-29" #118 prio=5 os_prio=0 ti
public static String post(String url, String params){ log.info("post url:" + url + " params:" + params); String responseStr = ""; try(CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpPost httpPost = new HttpPost(url); StringEntity stringEntity = new StringEntity(params, Charset.forName("UTF-8")); httpPost.setHeader("Content-type", "application/json"); httpPost.setEntity(stringEntity); Closeable
日志如下: 2017-12-25 09:08:26,001 [http-nio-8080-exec-8] DEBUG o.a.h.wire - http-outgoing-322 >> "Connection: Keep-Alive[\r][\n]" 2017-12-25 09:08:26,00
java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.S...
socket中服务器与客户端进行通信,当其中一方调用close(即这一方会发送一个fin)关闭套接字之后,另一方read()会返回一个0。 我之前编写的一个服务器与客户端通信(一个服务器只连接一个客户端):服务器开两个进程,一个用于接收客户端发送的数据,另一个进程用于 向客户端发送数据。客户端开两个进程也是一个用于发送数据一个用于接收数据。由于创建了两个进程,那么套接字的引用计数都为2,只有...
一次完整的http请求包含 建立连接、传输数据、断开连接。 ConnectionTimeOut 是指建立连接的时候就超时了,没有连接到对方。表现形式:java.net.ConnectException: Connection refused: connect SocketTimeOut 是指已经建立连接了,但是在服务器没有在规定的时间内传输数据给客户端,有可能是对方处理数据慢导致超时,表现形式:java.net.SocketTimeoutException: Read timed out
连接池技术作为创建和管理连接的缓冲池技术,目前已广泛用于诸如数据库连接等长连接的维护和管理中,能够有效减少系统的响应时间,节省服务器资源开销。其优势主要有两个:其一是减少创建连接的资源开销,其二是资源的访问控制。连接池管理的对象是长连接,对于HTTP连接是否适用,我们需要首先回顾一下长连接和短连接。 所谓长连接是指客户端与服务器端一旦建立连接以后,可以进行多次数据传输而不需重新建立连接,而短连接则每次数据传输都需要客户端和服务器端建立一次连接。长连接的优势在于省去了每次数据传输连接建立的时间开销,能够大幅度
public class TestMain { public static void main(String[] args) throws NSQException, TimeoutException { ExecutorService pool = Executors.newCachedThreadPool(); //超时设置 RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(400).setSocketTimeout(400).build(); request.setEntity(s); requ...
查阅发现一个jdk的问题 SocketInputStream.socketRead0 can hang even with soTimeout set Bug Database_socketRead0 2.官方在16年以做出修改 3.comments The customer tested with the attached native tests. Both the poll (which mi
HttpClient 发送 POST 请求时,有可能会遇到服务器返回 302 的情况。这种情况通常是因为服务器需要进行重定向,而 HttpClient 默认是不会自动进行重定向的。 解决这个问题的方法,可以通过设置 HttpClient 的 RedirectStrategy 属性来实现自动重定向。下面是一个示例代码: ```java CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost(url); List<NameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("param1", "value1")); params.add(new BasicNameValuePair("param2", "value2")); httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(60000) .setConnectTimeout(60000) .setRedirectsEnabled(true) // 允许自动重定向 .build(); httpPost.setConfig(requestConfig); CloseableHttpResponse httpResponse = httpClient.execute(httpPost); int statusCode = httpResponse.getStatusLine().getStatusCode(); if (statusCode == HttpStatus.SC_OK) { // 处理正常情况的响应结果 } else if (statusCode == HttpStatus.SC_MOVED_TEMPORARILY || statusCode == HttpStatus.SC_MOVED_PERMANENTLY) { // 处理重定向情况 String redirectUrl = httpResponse.getFirstHeader("Location").getValue(); // 重新发送请求,并处理响应结果 // ... 在上面的代码中,我们通过设置 RequestConfig 的 setRedirectsEnabled(true) 来允许自动重定向。当服务器返回 302 状态码时,我们可以从响应头中获取重定向的 URL,然后重新发送请求并处理响应结果。