Потоковые большие файлы в сервлете Java - PullRequest
39 голосов
/ 11 сентября 2008

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

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

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

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

Любые мысли приветствуются. Спасибо.

Ответы [ 8 ]

52 голосов
/ 11 сентября 2008

По возможности не следует хранить все содержимое файла для обслуживания в памяти. Вместо этого получите 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();

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

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

Редактировать - Относительно:

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

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

  1. Исходная HTML-страница содержит ссылки для загрузки вашего веб-приложения
  2. Пользователь нажимает на ссылку для скачивания
  3. Ваше веб-приложение генерирует URL-адрес S3, который включает ключ, срок действия которого истекает, скажем, через 5 минут.
  4. Отправка HTTP-перенаправления клиенту с URL-адресом из шага 3.
  5. Пользователь загружает файл с S3. Это работает, даже если загрузка занимает более 5 минут - после начала загрузки она может продолжаться до конца.
17 голосов
/ 11 сентября 2008

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

10 голосов
/ 23 апреля 2014

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

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

2 голосов
/ 11 сентября 2008

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

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

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

  1. Поместите -XX: + HeapDumpOnOutOfMemoryError в параметры запуска JVM, на всякий случай
  2. использовать jmap на работающей JVM ( jmap -histo ) под нагрузкой
  3. Анализировать метрики (jmap -histo out put или jhat посмотреть на ваш дамп кучи). Вполне возможно, что ваша нехватка памяти приходит откуда-то неожиданно.

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

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

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

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

1 голос
/ 11 сентября 2008

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

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

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

0 голосов
/ 01 октября 2009

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

0 голосов
/ 11 сентября 2008

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

0 голосов
/ 11 сентября 2008

Вы должны проверить две вещи:

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