Подтвердить что ты не робот

Избегайте ожидания на потоках сервлета

Мой Servler проводит некоторое время при чтении request.getInputStream() и записи на response.getOutputStream(). В конечном итоге это может быть проблемой, поскольку она блокирует поток ни для чего, кроме как чтение/запись буквально нескольких байтов в секунду. (*)

Мне никогда не интересны данные частичного запроса, обработка не должна запускаться до того, как запрос будет полностью доступен. Аналогично для ответа.

Я предполагаю, что асинхронный IO решил бы это, но мне интересно, какой правильный путь. Возможно, сервлет Filter заменил ServletInputStream на обернутый ByteArrayInputStream, используя request.startAsync и вызывая цепочку сервлетов после того, как собрал весь вход?

  • Есть ли такой фильтр?
  • Должен ли я написать один или использовать другой подход?

Обратите внимание, что я имею в виду - не тратить потоки на медленные потоки сервлета. Это не то же самое, что startAsync, которое позволяет избежать потоков, ожидающих только какого-либо события.

И да, на данный момент это будет преждевременная оптимизация.

Мой цикл чтения по запросу

В моем текущем методе ввода потока нет ничего интересного, но здесь вы:

private byte[] getInputBytes() throws IOException {
    ServletInputStream inputStream = request.getInputStream();
    final int len = request.getContentLength();
    if (len >= 0) {
        final byte[] result = new byte[len];
        ByteStreams.readFully(inputStream, result);
        return result;
    } else {
        return ByteStreams.toByteArray(inputStream);
    }
}

Это все и блокируется, когда данные недоступны; ByteStreams - из Гуавы.

Резюме моего понимания до сих пор

Как четко говорится в ответах, невозможно работать с потоками сервлетов, не тратя нити на них. Ни архитектура сервлета, ни общая реализация не раскрывают ничего, что позволяет сказать "буферизировать все данные и называть меня только тогда, когда вы собрали все", хотя они используют NIO и могут это сделать.

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

Фактически поддерживаемый случай

Учитывая, что многие отрицательные ответы, я не уверен, но это выглядит как моя цель

чтобы избежать траты потоков на медленных потоках сервлетов

на самом деле полностью поддерживается: начиная с версии 3.1, ServletInputStream.html#setReadListener, который, как представляется, предназначен именно для этого. Поток, выделенный для обработки Servlet#Service изначально вызывает request.startAsync(), присоединяет слушателя и возвращается в пул, просто возвращаясь из service. Слушатель реализует onDataAvailable(), который вызывается, когда его можно читать без блокировки, добавляет кусок данных и возвращает. В onAllDataRead() я могу выполнить всю обработку собранных данных.

Здесь пример, как это можно сделать с Jetty. Он также охватывает неблокирующий выход.


(*) В лог файлах я могу видеть запросы, занимающие до восьми секунд, которые тратятся на чтение ввода (100-байтовый заголовок + 100 байт данных). Такие случаи встречаются редко, но они происходят, хотя сервер в основном бездействует. Поэтому я предполагаю, что это мобильный клиент с очень плохим соединением (некоторые из наших пользователей подключаются из мест с такой плохой связностью).

4b9b3361

Ответ 1

HttpServletRequest#startAsync() не подходит для этого. Это полезно только для таких вещей, как веб-сокеты и хороший SSE. Кроме того, JSR356 Web Socket API построен поверх него.

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

Чтобы сохранить потоки, вам действительно нужен сервлетконтейнер, который поддерживает NIO и при необходимости включит эту функцию. С NIO один поток может обрабатывать столько TCP-соединений, сколько позволяет доступная память кучи, а не один поток выделяется для каждого TCP-соединения. Затем, в вашем сервлете, вам совсем не нужно беспокоиться об этой деликатной задаче ввода-вывода.

Почти все современные servletcontainers поддерживают его: Undertow (WildFly), Grizzly (GlassFish/Payara), Tomcat, Jetty и т.д. Некоторые из них по умолчанию включены, другие требуют дополнительной настройки. Просто отправьте свою документацию, используя ключевое слово "NIO".

Если вы действительно хотите также сохранить сам поток запросов сервлета, вам в основном нужно сделать шаг назад, сбросить сервлеты и реализовать пользовательскую службу на основе NIO поверх существующего разъема NIO (Undertow, Grizzly, Jetty и т.д.).

Ответ 2

  • Вы не можете. Контейнер Servlet выделяет поток для запроса и что его конец выделяется. Это модель. Если вам это не нравится, вам придется прекратить использование сервлета.
  • Даже если вы можете решить (1), вы не можете запускать асинхронный ввод-вывод во входном потоке.
  • Способ обработки медленных запросов состоит в том, чтобы отключить их, установив соответствующий параметр для любого контейнера, который вы используете... если у вас действительно есть проблема, и это далеко не ясно, что вы действительно делаете, в основном бездействующий сервер, и это происходит редко.
  • Ваш цикл чтения делает различие без разницы. Просто прочитайте поток ввода запроса до конца. Контейнер сервлета уже гарантирует, что конец потока будет иметь длину содержимого, если он предоставлен.

Ответ 3

Существует класс под названием org.apache.catalina.connector.CoyoteAdapter, который является классом, который получает маршализированный запрос из рабочего потока TCP. У него есть метод под названием "сервис", который выполняет основную часть тяжелого подъема. Этот метод вызывается другим классом: org.apache.coyote.http11.Http11Processor, который также имеет метод с тем же именем.

Мне интересно, что я вижу так много крючков в коде для обработки async io, что заставляет меня задаться вопросом, не является ли это уже не встроенной функцией контейнера? Во всяком случае, с моими ограниченными знаниями, лучший способ, с помощью которого я могу реализовать эту функцию, - создать класс:

public class MyAsyncReqHandlingAdapter extends CoyoteAdapter и @Override service() и переверните свой собственный... У меня нет времени посвятить этому сейчас, но я могу вернуться в будущем.

В этом методе вам понадобится способ определить медленные запросы и обработать их, передав их одному однопоточному nio-процессору и "заполнив" запрос на этом уровне, который, учитывая исходный код:

https://github.com/apache/tomcat/blob/075920d486ca37e0286586a9f017b4159ac63d65/java/org/apache/coyote/http11/Http11Processor.java

https://github.com/apache/tomcat/blob/3361b1321201431e65d59d168254cff4f8f8dc55/java/org/apache/catalina/connector/CoyoteAdapter.java

Вы должны уметь выяснить, как это сделать. Интересный вопрос и да, это можно сделать. Ничего, что я вижу в спецификации, не может...