2008-09-11 4 views
36

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

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

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

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

Любые мысли будут оценены. Благодарю.

ответ

47

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

Edit - Относительно:

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

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

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

Хмм, поскольку длина содержимого не задана, контейнер сервлета должен буферизировать, потому что ему нужно установить заголовок длины контента, прежде чем он сможет передавать любые данные. Не уверен, сколько памяти вы сохраните? – 2012-02-01 15:54:36

+1

Питер, если вы не можете указывать пользователям прямо на URL-адрес облачной службы, и вы хотите установить заголовок длины контента, и вы еще не знаете размер, и вы не можете запросить облачную службу для размера, тогда я предполагаю, что ваш Лучшая ставка - сначала передать в временный файл на сервере. Конечно, сохранение копии на сервере перед отправкой первого байта клиенту может привести к тому, что пользователь будет считать запрос неудачным в зависимости от того, как долго переносится облако -> сервер. – 2012-02-13 03:55:37

17

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

0

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

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

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

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

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

0

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

2

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

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

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

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

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

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

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

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

0

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

10

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

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^