2015-02-28 3 views
33

Performing миллионы HTTP запросов с различными библиотеками Java дают мне темы, вешали на:Как предотвратить зависания на SocketInputStream.socketRead0 в Java?

java.net.SocketInputStream.socketRead0()

который является native функцией.

Я пытался настроить Apche HTTP Client и RequestConfig иметь тайм-ауты (я надеюсь) everythig, что возможно, но до сих пор, у меня есть (возможно бесконечное) висит на socketRead0. Как избавиться от них?

Компонент Hung составляет около ~ 1 на 10000 запросов (до 10000 различных хостов), и он может длиться, вероятно, навсегда (я подтвердил, что поток висел по-прежнему действует через 10 часов).

JDK 1.8 на Windows 7.

Мой 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 source

Примечание: На самом деле у меня есть некоторые "трюк" - Я могу запланировать .getConnectionManager().shutdown() в другой Thread с отменой Future если запрос закончен правильно, но он разрознен, а также он убивает весь HttpClient, а не только тот единственный запрос.

+0

Ну, они собираются заблокировать до тех пор, пока не поступят данные или не истечет время ожидания. Вы имеете в виду, что эти потоки постоянно заблокированы и не синхронизированы? – EJP

+0

Да, я имею в виду, что он вешает вечно (я проверил через 6 часов scenerio) –

+0

Правильно ли, что 'HttpClientBuilder' имеет' builder.disableRedirectHandling() 'и' RequestConfig' имеет '.setRedirectsEnabled (true)'? –

ответ

1

Для HTTP-клиента Apache (блокировка) Я нашел лучшее решение для getConnectionManager(). и выключить его.

Таким образом, в решении высокой надежности я просто планировать завершение работы в другом потоке и в запросе случае не полного закрытия я в вниз от другого потока

0

не Учитывая никто другой ответил до сих пор, вот мое взятие

Ваша установка времени ожидания выглядит совершенно нормально для меня. Причина, по которой некоторые запросы, по-видимому, постоянно блокируются в вызове java.net.SocketInputStream#socketRead0(), скорее всего, связана с комбинацией неправильных серверов и локальной конфигурации. Тайм-аут сокета определяет максимальный период бездействия между двумя последовательными операциями чтения/вывода (или, другими словами, двумя последовательными входящими пакетами). Установка тайм-аута вашего сокета составляет 5000 миллисекунд. Пока противоположная конечная точка продолжает отправлять пакет каждые 4 999 миллисекунд для сообщения, закодированного в блоке, запрос никогда не будет тайм-аутом и в конечном итоге отправит большую часть своего времени в java.net.SocketInputStream#socketRead0(). Вы можете узнать, действительно ли это происходит, запустив HttpClient с включенным протоколом проводки.

+1

Таймаут чтения сокета определяет максимальный интервал между входом метода 'recv()' и приходом данных. Он не имеет ничего общего с интервалом между операциями чтения или между пакетами. – EJP

+0

, который является операцией чтения с точки зрения Java. – oleg

+0

Исправить. Не изменяет ошибку в вашем ответе. Таймер запускается, когда вы вводите recv() или read(), и останавливается, когда он истекает, или данные или поступают, или EOS, или возникает ошибка. Не имеет ничего общего с интервалом между двумя чтениями или двумя пакетами. То, что вы написали выше, не имеет смысла. Это означает, что вы не можете получить таймаут при первом чтении, например. И время между двумя чтениями - это не то же самое, что время между двумя пакетами в первую очередь. – EJP

3

Вы должны рассмотреть Неблокирующий HTTP-клиент, такой как Grizzly или Netty, которые не имеют операций блокировки для зависания потока.

+0

Хорошая идея и, возможно, я закончу с этим, но я просто хотел уточнить, как добиться этого с блокировкой Http (чтобы вызвать socketRead0, но не зависает). Так что другой ответ принят. Благодарю. Я бы добавил, что Apache Http Client также имеет асинхронную, не блокирующую версию. –

6

Как Clint said, следует рассмотреть неблокируемый HTTP-клиент, или (видя, что вы используете в Apache HTTPClient) реализовать Multithreaded request execution для предотвращения возможных зависаний основного приложения нити (это не решает проблему, но лучше перезагрузите приложение, потому что оно заморожено). Во всяком случае, вы установите setStaleConnectionCheckEnabled свойство, но несвежая проверка соединения не является надежной 100%, от Apache HTTPClient учебника:

Одним из основных недостатков классической блокировки модель ввод/вывод , что сетевой сокет может реагировать на события ввода-вывода только при блокировке в операции ввода-вывода.Когда соединение будет возвращено менеджеру, его можно сохранить в живых, однако он не сможет контролировать состояние гнезда и реагировать на любые события ввода-вывода. Если соединение закрывается на стороне сервера, соединение на стороне клиента не может обнаружить изменение в состоянии соединения (и реагировать соответствующим образом, закрыв на своем конце разъем ).

HttpClient пытается смягчить проблему путем проверки того, является ли «несвежим» соединением, что больше не действует, так как он был закрыт на стороне сервера, перед использованием соединения для выполнения запроса HTTP. Проверка устаревших соединений не на 100% надежна и добавляет от 10 до 30 мс для каждого выполнения запроса.

Апач HttpComponents экипажа рекомендует реализацию Connection eviction policy

единственно возможного решения, которое не включает в себя один потоке на розетки модели для холостых подключений выделенного монитора нить используется для которые считаются истекли из-за длительного периода бездействия. Поток монитора может периодически вызывать ClientConnectionManager # closeExpiredConnections() для закрытия всех истекших подключений и выключения закрытых подключений из пула. Он может также дополнительно вызвать метод ClientConnectionManager # closeIdleConnections() , чтобы закрыть все соединения, которые простаивали в течение заданного периода времени.

Посмотри на образец коде выселения политики раздел Подключения и попытаться реализовать его в своем приложении, наряду с исполнением запроса многопоточного, я думаю, что реализация обоего механизмов позволит предотвратить ваши нежелательные зависания.

+0

Спасибо за подробный ответ. Ссылка на политику выселения заключалась в том, что я ищу. Я проделал аналогичную работу со всем диспетчером соединений, теперь я знаю, как это сделать на реальных отдельных подключениях. Благодарю. Но, наконец, возможно, я переключусь на неблокирующий клиент. –

+1

Политика выселения предназначена для удаления устаревших соединений _idle_. Он не будет влиять на то, что когда-либо было на соединениях, арендованных из пула и используемых для выполнения запросов (и заблокированных в операции чтения). – oleg

+0

@oleg Если это так, я не принял ответа. Возможно, что-то новое придет. –

13

Хотя этот вопрос упоминает Windows, у меня такая же проблема для Linux. Оказывается, есть недостаток в том, как JVM реализует блокирующий сокет таймаут:

Чтобы подытожить, тайм-аут для блокирующих сокетов осуществляется с помощью вызова poll на Linux (и select на Windows), чтобы определить, какие данные доступны до вызова recv. Однако, по крайней мере, в Linux оба метода могут ложно указывать, что данные доступны, когда это не так, что приводит к блокировке recv.

Из опроса (2) мужчину страница ОШИБКИ раздела:

Смотрите обсуждение ложных уведомлений готовности в разделе багов выбор (2).

От выбора (2) человек страницы BUGS раздел:

Под Linux, выберите() может сообщить дескриптора файла сокета, как "готовый для чтения", в то время как все же последующие чтения блоков. Это может быть , например, когда данные приходят, но при проверке имеет неправильную контрольную сумму и отбрасывается. Могут быть и другие обстоятельства , в которых файловый дескриптор ложно сообщается как готовый. Таким образом, может быть более безопасным для использования O_NONBLOCK на сокетах, которые не должны блокироваться.

Код клиента Apache HTTP немного трудно следовать, но appears, что истечение соединение устанавливается только для HTTP Keep-Alive соединений (которые вы отключили) и неопределенна, если сервер не указано иное. Поэтому, как указывает oleg, подход Connection eviction policy не будет работать в вашем случае и на него нельзя вообще полагаться.

+1

Похоже, ошибка была исправлена ​​в сентябре. Вы перестали испытывать проблему? – Arya

4

У меня есть более 50 машин, которые делают около 200к запросов/день /машина. Они запускают Amazon Linux AMI 2017.03. Раньше у меня был jdk1.8.0_102, теперь у меня есть jdk1.8.0_131. Я использую apacheHttpClient и OKHttp как библиотеки скремблирования.

Каждая машина работала на 50 нитей, а иногда потоки теряются. После профилирования с Youkit Java профайлером я получил

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 (ранний доступ). Я установил его на одной из наших машин. Теперь я жду, чтобы увидеть хорошие результаты.

+0

Спасибо за обновление, пожалуйста, сообщите о результатах. –

+0

Не повезло. Он застрял всю ночь. Я попытаюсь связаться с ними в oracle об ошибке. Он был отмечен как разрешенный. А также найти обходное решение (прервать соединение из другого потока), поскольку я устал перезапускать машины каждый день. –

+1

@Stefan Спасибо за информацию. Если вы получили ошибку, поданную против Windows JDK, отправьте номер ошибки в этом вопросе stackoverflow. – buzz3791

1

Я столкнулся с той же проблемой, используя общий http-клиент Apache.

Там довольно простой обходной путь (который не требует закрытия диспетчера подключений вниз):

Нам нужно выполнить запрос от вопроса в новом потоке с некоторыми дополнительными расходами: Запрос

  • запустить в отдельная резьба, закрыть запрос и отпустить его соединение в другой резьбе, прервать висящую нить
  • не запускать EntityUtils.consumeQuietly(response.getEntity()) в конце блока (потому что он висит на «мертвом» соединении)

Во-первых, добавить интерфейс

interface RequestDisposer { 
    void dispose(); 
} 

Execute запросов HTTP в новом потоке

final AtomicReference<RequestDisposer> requestDisposer = new AtomicReference<>(null); 

final Thread thread = new Thread(() -> { 
    final HttpGet request = new HttpGet("http://my.url"); 
    final RequestDisposer disposer =() -> { 
     request.abort(); 
     request.releaseConnection(); 
    }; 
    requestDiposer.set(disposer); 

    try (final CloseableHttpResponse response = httpClient.execute(request))) { 
     ... 
    } finally { 
     disposer.dispose(); 
    } 
};) 
thread.start() 

Вызов dispose() в главном потоке, чтобы закрыть подвешивания Connection

requestDisposer.get().dispose(); // better check if it's not null first 
thread.interrupt(); 
thread.join(); 

Это исправили проблему для меня ,

Мой StackTrace выглядел следующим образом:

java.lang.Thread.State: RUNNABLE 
at java.net.SocketInputStream.socketRead0(Native Method) 
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) 
at java.net.SocketInputStream.read(SocketInputStream.java:171) 
at java.net.SocketInputStream.read(SocketInputStream.java:141) 
at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:139) 
at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:155) 
at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:284) 
at org.apache.http.impl.io.ChunkedInputStream.getChunkSize(ChunkedInputStream.java:253) 
at org.apache.http.impl.io.ChunkedInputStream.nextChunk(ChunkedInputStream.java:227) 
at org.apache.http.impl.io.ChunkedInputStream.read(ChunkedInputStream.java:186) 
at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:137) 
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) 
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) 
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) 

Кому это может быть интересно это легко размножается, прервать поток без прерывания запроса и освобождения соединения (соотношение составляет около 1/100). Windows 10, версия 10.0. jdk8.151-x64.