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

Потоковая передача больших файлов в сервлет Java

Я создаю java-сервер, который нужно масштабировать. Один из сервлетов будет обслуживать изображения, хранящиеся в Amazon S3.

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

Мой вопрос: есть ли какая-либо лучшая практика в том, как закодировать сервлет Java для потоковой передачи большого ( > 200k) ответа на браузер при чтении из базы данных или другого облачного хранилища?

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

Любые мысли будут оценены. Спасибо.

4b9b3361

Ответ 1

Если возможно, вы не должны хранить все содержимое файла, который будет обслуживаться в памяти. Вместо этого загрузите InputStream для данных и скопируйте данные в Servlet OutputStream по частям. Например:

ServletOutputStream out = response.getOutputStream();
InputStream in = [ code to get source input stream ];
String mimeType = [ code to get mimetype of data to be served ];
byte[] bytes = new byte[FILEBUFFERSIZE];
int bytesRead;

response.setContentType(mimeType);

while ((bytesRead = in.read(bytes)) != -1) {
    out.write(bytes, 0, bytesRead);
}

// do the following in a finally block:
in.close();
out.close();

Я согласен с тобой, вместо этого вы должны указывать их на URL-адрес S3.

Что касается исключения OOM, вы уверены, что оно связано с обслуживанием данных изображения? Скажем, ваша JVM имеет 256 МБ "лишней" памяти для использования для обработки данных изображения. С помощью Google "256 МБ /200 КБ" = 1310. Для 2 ГБ "лишней" памяти (в наши дни очень разумная сумма) может поддерживаться более 10 000 одновременных клиентов. Тем не менее, 1300 одновременных клиентов - довольно большое число. Это тот тип нагрузки, который вы испытали? Если нет, возможно, вам придется искать в другом месте причину исключения OOM.

Изменить - Относительно:

В этом случае изображения могут содержать конфиденциальные данные...

Когда я прочитал документацию S3 несколько недель назад, я заметил, что вы можете создавать ключи с истечением срока действия, которые могут быть привязаны к URL-адресам S3. Таким образом, вам не придется открывать файлы на S3 для публики. Мое понимание техники:

  • На начальной странице HTML есть ссылки для загрузки на ваш webapp
  • Пользователь нажимает на ссылку для скачивания.
  • Ваш webapp генерирует URL-адрес S3, который включает в себя ключ, срок действия которого истекает, скажем, 5 минут.
  • Отправьте HTTP-перенаправление клиенту с URL-адресом с шага 3.
  • Пользователь загружает файл с S3. Это работает, даже если загрузка занимает более 5 минут - после начала загрузки она может продолжаться до завершения.

Ответ 2

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

Ответ 3

Я видел много таких кодов, как ответ john-vasilef (в настоящее время принятый), жесткий фрагмент чтения цикла из одного потока и запись их в другой поток.

Аргумент, который я бы сделал, - против ненужного дублирования кода, в пользу использования Apache IOUtils. Если вы уже используете его в другом месте или если другая библиотека или фрейм, которые вы используете, уже зависит от нее, это одна строка, которая известна и хорошо протестирована.

В следующем коде я передаю объект из Amazon S3 клиенту в сервлет.

import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;

InputStream in = null;
OutputStream out = null;

try {
    in = object.getObjectContent();
    out = response.getOutputStream();
    IOUtils.copy(in, out);
} finally {
    IOUtils.closeQuietly(in);
    IOUtils.closeQuietly(out);
}

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

Ответ 4

Я согласен с обоими toby и John Vasileff - S3 отлично подходит для загрузки больших медиа-объектов, если вы можете терпеть связанные с этим проблемы. (Экземпляр собственного приложения делает это для FLV и MP4 10-1000 МБ.) Например: никаких частичных запросов (заголовок диапазона байтов). Нужно обрабатывать это "вручную", время от времени и т.д.

Если это не вариант, код Джона выглядит хорошо. Я обнаружил, что байтовый буфер 2k FILEBUFFERSIZE является наиболее эффективным в методах микрообработки. Другим вариантом может быть общий FileChannel. (FileChannels являются потокобезопасными.)

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

  • Место -XX: + HeapDumpOnOutOfMemoryError в вас параметры запуска JVM, на всякий случай
  • использовать jmap на запущенной JVM (jmap -histo <pid> ) под загрузкой
  • Проанализируйте метрики (jmap -histo out put, или посмотрите, как выглядит ваш куча памяти). Очень хорошо, может быть, из-за чего-то неожиданное вышло из памяти.

Есть, конечно, другие инструменты, но jmap и jhat поставляются с Java 5+ "из коробки"

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

А, я не думаю, что вы не можете этого сделать. И даже если бы это было возможно, это звучит сомнительно. Поток tomcat, управляющий соединением, должен контролироваться. Если вы испытываете головокружение потоков, то увеличивайте количество доступных потоков в. /conf/server.xml. Опять же, метрики - это способ обнаружить это - не догадывайтесь.

Вопрос: Вы также работаете на EC2? Каковы ваши параметры запуска Tomcat JVM?

Ответ 5

toby прав, вы должны указывать прямо на S3, если можете. Если вы не можете, вопрос немного расплывчато, чтобы дать точный ответ: Насколько велика ваша куча java? Сколько потоков открыто одновременно, когда у вас заканчивается память?
Насколько велика ваша читаемая запись/буфер (8K - хорошая)?
Вы читаете 8K из потока, а затем записываете 8k на выход, правильно? Вы не пытаетесь прочитать все изображение с S3, буферизировать его в памяти, а затем отправить все сразу?

Если вы используете буферы 8K, у вас может быть 1000 параллельных потоков в ~ 8 мегабайтах пространства кучи, поэтому вы определенно делаете что-то неправильно....

Кстати, я не выбрал 8K из тонкого воздуха, это размер по умолчанию для буферов сокетов, отправьте больше данных, скажем 1Meg, и вы будете блокировать стек tcp/ip, содержащий большой объем памяти.

Ответ 6

Вам нужно проверить две вещи:

  • Вы закрываете поток? Очень важно
  • Возможно, вы предоставляете потоковые соединения "бесплатно". Поток невелик, но многие потоки одновременно могут украсть всю вашу память. Создайте пул, чтобы вы не могли одновременно работать с определенным количеством потоков

Ответ 7

В дополнение к тому, что предложил Джон, вы должны повторно очистить выходной поток. В зависимости от вашего веб-контейнера возможно, что он кэширует части или даже весь ваш выход и сбрасывает его один раз (например, для вычисления заголовка Content-Length). Это сгорело бы немного памяти.

Ответ 8

Если вы можете структурировать свои файлы так, чтобы статические файлы были отдельными и в собственном ведре, самую быструю производительность сегодня можно, вероятно, достичь, используя Amazon S3 CDN, CloudFront.