如何防止Java中的SocketInputStream.socketRead0挂起?

Pio*_*ler 51 java sockets timeout http apache-httpclient-4.x

使用不同的Java库执行数百万个HTTP请求会让我挂起线程:

java.net.SocketInputStream.socketRead0()

哪个是native功能.

我试图设置Apche Http Client,并且RequestConfig(我希望)有一些可能的超时,但我仍然(可能无限)挂起socketRead0.如何摆脱它们?

挂起比率约为每10000个请求约1个(到10000个不同的主机)并且它可能永远持续(我已确认线程挂起仍然有效,10小时后仍然有效).

Windows 7上的JDK 1.8.

我的HttpClient工厂:

SocketConfig socketConfig = SocketConfig.custom()
            .setSoKeepAlive(false)
            .setSoLinger(1)
            .setSoReuseAddress(true)
            .setSoTimeout(5000)
            .setTcpNoDelay(true).build();

    HttpClientBuilder builder = HttpClientBuilder.create();
    builder.disableAutomaticRetries();
    builder.disableContentCompression();
    builder.disableCookieManagement();
    builder.disableRedirectHandling();
    builder.setConnectionReuseStrategy(new NoConnectionReuseStrategy());
    builder.setDefaultSocketConfig(socketConfig);

    return HttpClientBuilder.create().build();
Run Code Online (Sandbox Code Playgroud)

我的RequestConfig工厂:

    HttpGet request = new HttpGet(url);

    RequestConfig config = RequestConfig.custom()
            .setCircularRedirectsAllowed(false)
            .setConnectionRequestTimeout(8000)
            .setConnectTimeout(4000)
            .setMaxRedirects(1)
            .setRedirectsEnabled(true)
            .setSocketTimeout(5000)
            .setStaleConnectionCheckEnabled(true).build();
    request.setConfig(config);

    return new HttpGet(url);
Run Code Online (Sandbox Code Playgroud)

OpenJDK socketRead0源码

注意:实际上我有一些"技巧" - 我可以.getConnectionManager().shutdown()在其他方面安排Thread取消,Future如果请求已正确完成,但它被删除,并且它也杀死了整个HttpClient,而不仅仅是单个请求.

Tre*_*son 19

虽然这个问题提到了Windows,但我在Linux上遇到了同样的问题.看来JVM实现阻塞套接字超时的方式有一个缺陷:

总而言之,阻塞套接字的超时是通过调用pollLinux(和selectWindows)来确定数据在调用之前是否可用来实现的recv.但是,至少在Linux上,这两种方法都可以虚假地指示数据在不存在时可用,从而导致recv无限期阻塞.

来自poll(2)手册页BUGS部分:

请参阅select(2)的BUGS部分下的虚假就绪通知的讨论.

从select(2)手册页BUGS部分:

在Linux下,select()可以将套接字文件描述符报告为"准备好读取",而不是后续的读取块.这可能例如在数据到达时发生但在检查时具有错误的校验和并被丢弃.可能存在其他情况,其中虚假地报告文件描述符为就绪.因此,在不应阻塞的套接字上使用O_NONBLOCK可能更安全.

Apache HTTP客户端代码有点难以理解,但似乎只为HTTP保持活动连接(已禁用)设置了连接到期,并且除非服务器另行指定,否则它是无限期的.因此,正如oleg所指出的,连接驱逐策略方法在您的情况下不起作用,并且一般不能依赖.


vza*_*llo 13

正如Clint所说,您应该考虑使用非阻塞HTTP客户端,或者(看到您正在使用Apache Httpclient)实现多线程请求执行以防止主应用程序线程可能挂起(这不能解决问题,但比重启更好)你的应用程序因为冻结了).无论如何,您setStaleConnectionCheckEnabled从Apache Httpclient教程设置属性,但过时的连接检查不是100%可靠的:

经典阻塞I/O模型的主要缺点之一是网络套接字只有在I/O操作中被阻塞时才能对I/O事件作出反应.当连接释放回管理器时,它可以保持活动状态,但它无法监视套接字的状态并对任何I/O事件做出反应.如果连接在服务器端关闭,则客户端连接无法检测连接状态的变化(并通过关闭其端部的套接字来做出适当的反应).

HttpClient尝试通过测试连接是否"陈旧"来缓解此问题,该连接在使用连接执行HTTP请求之前不再有效,因为它在服务器端关闭.过时的连接检查不是100%可靠,并且每次请求执行都会增加10到30 ms的开销.

Apache HttpComponents工作人员建议实施连接驱逐策略

唯一可行的解​​决方案是,每个套接字模型不涉及空闲连接的一个线程是一个专用的监视器线程,用于驱逐由于长时间不活动而被视为过期的连接.监视器线程可以定期调用ClientConnectionManager#closeExpiredConnections()方法来关闭所有过期的连接并从池中驱逐关闭的连接.它还可以选择调用ClientConnectionManager#closeIdleConnections()方法来关闭在给定时间段内空闲的所有连接.

看一下Connection eviction policy部分的示例代码,并尝试在您的应用程序中实现它以及多线程请求执行,我认为这两种机制的实现将防止您的意外挂起.

  • 驱逐策略旨在删除陈旧的 _idle_ 连接。它不会对从池租用并用于执行请求(并在读取操作中被阻止)的连接产生任何影响。 (2认同)

Cli*_*int 5

您应该考虑使用非阻塞HTTP客户端(例如GrizzlyNetty),它们没有阻塞操作来挂起线程。


Ste*_*tei 5

我有超过50台机器,每天/机器约200k个请求.他们正在运行Amazon Linux AMI 2017.03.我以前有jdk1.8.0_102,现在我有jdk1.8.0_131.我使用apacheHttpClient和OKHttp作为抓取库.

每台机器运行50个线程,有时线程会丢失.在使用Youkit java profiler进行分析后,我得到了

ScraperThread42 State: RUNNABLE CPU usage on sample: 0ms
java.net.SocketInputStream.socketRead0(FileDescriptor, byte[], int, int, int) SocketInputStream.java (native)
java.net.SocketInputStream.socketRead(FileDescriptor, byte[], int, int, int) SocketInputStream.java:116
java.net.SocketInputStream.read(byte[], int, int, int) SocketInputStream.java:171
java.net.SocketInputStream.read(byte[], int, int) SocketInputStream.java:141
okio.Okio$2.read(Buffer, long) Okio.java:139
okio.AsyncTimeout$2.read(Buffer, long) AsyncTimeout.java:211
okio.RealBufferedSource.indexOf(byte, long) RealBufferedSource.java:306
okio.RealBufferedSource.indexOf(byte) RealBufferedSource.java:300
okio.RealBufferedSource.readUtf8LineStrict() RealBufferedSource.java:196
okhttp3.internal.http1.Http1Codec.readResponse() Http1Codec.java:191
okhttp3.internal.connection.RealConnection.createTunnel(int, int, Request, HttpUrl) RealConnection.java:303
okhttp3.internal.connection.RealConnection.buildTunneledConnection(int, int, int, ConnectionSpecSelector) RealConnection.java:156
okhttp3.internal.connection.RealConnection.connect(int, int, int, List, boolean) RealConnection.java:112
okhttp3.internal.connection.StreamAllocation.findConnection(int, int, int, boolean) StreamAllocation.java:193
okhttp3.internal.connection.StreamAllocation.findHealthyConnection(int, int, int, boolean, boolean) StreamAllocation.java:129
okhttp3.internal.connection.StreamAllocation.newStream(OkHttpClient, boolean) StreamAllocation.java:98
okhttp3.internal.connection.ConnectInterceptor.intercept(Interceptor$Chain) ConnectInterceptor.java:42
okhttp3.internal.http.RealInterceptorChain.proceed(Request, StreamAllocation, HttpCodec, Connection) RealInterceptorChain.java:92
okhttp3.internal.http.RealInterceptorChain.proceed(Request) RealInterceptorChain.java:67
okhttp3.internal.http.BridgeInterceptor.intercept(Interceptor$Chain) BridgeInterceptor.java:93
okhttp3.internal.http.RealInterceptorChain.proceed(Request, StreamAllocation, HttpCodec, Connection) RealInterceptorChain.java:92
okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(Interceptor$Chain) RetryAndFollowUpInterceptor.java:124
okhttp3.internal.http.RealInterceptorChain.proceed(Request, StreamAllocation, HttpCodec, Connection) RealInterceptorChain.java:92
okhttp3.internal.http.RealInterceptorChain.proceed(Request) RealInterceptorChain.java:67
okhttp3.RealCall.getResponseWithInterceptorChain() RealCall.java:198
okhttp3.RealCall.execute() RealCall.java:83
Run Code Online (Sandbox Code Playgroud)

我发现他们已经解决了这个问题

https://bugs.openjdk.java.net/browse/JDK-8172578

在JDK 8u152(早期访问).我已将它安装在我们的一台机器上.现在我等着看到一些好的结果.


Pio*_*ler 3

对于 Apache HTTP 客户端(阻塞),我发现最好的解决方案是 getConnectionManager()。并关闭它。

因此,在高可靠性解决方案中,我只是在其他线程中安排关闭,如果请求未完成,我将从其他线程关闭