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




httppost超时时间 (5)

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

java.net.SocketInputStream.socketRead0()

这是native功能。

我试图设置Apche Http Client和RequestConfig以便在(我希望)everythig上超时,但仍然, 我有(可能是无限的)挂起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();

我的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);

OpenJDK socketRead0源码

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

https://code.i-harness.com


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

所以在高可靠性的解决方案中,我只是在其他线程中安排关闭,以防万一请求没有完成我正在关闭其他线程


您应该考虑像GrizzlyNetty这样没有阻塞操作来挂起线程的非阻塞HTTP客户端。


我有超过50台机器,每天可处理约20万件机器/机器。 他们正在运行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

我发现他们有一个解决方案

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

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


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

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

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

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

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

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


鉴于到目前为止没有其他人做出回应,这是我的看法

您的超时设置对我来说完全没问题。 某些请求似乎在java.net.SocketInputStream#socketRead0()调用中经常被阻止的原因可能是由于行为不当的服务器和本地配置的组合。 套接字超时定义了两个连续的i / o读取操作(或换句话说,两个连续的传入数据包)之间的最大不活动时间段。 套接字超时设置为5,000毫秒。 只要对端点继续为块编码消息每隔4,999毫秒发送一个数据包,请求就永远不会超时,最终会在java.net.SocketInputStream#socketRead0()阻止大部分时间被阻塞。 您可以通过运行打开有线记录的HttpClient来查明是否是这种情况。





apache-httpclient-4.x