Domanda

Sto costruendo un server Java che deve essere scalato.Uno dei servlet servirà le immagini archiviate in Amazon S3.

Recentemente sotto carico, ho esaurito la memoria nella mia VM ed è stato dopo aver aggiunto il codice per servire le immagini, quindi sono abbastanza sicuro che lo streaming di risposte servlet più grandi stia causando i miei problemi.

La mia domanda è :esiste una procedura consigliata su come codificare un servlet Java per trasmettere in streaming una risposta di grandi dimensioni (>200k) a un browser quando viene letta da un database o da un altro archivio cloud?

Ho preso in considerazione la possibilità di scrivere il file su un'unità temporanea locale e quindi di generare un altro thread per gestire lo streaming in modo che il thread servlet Tomcat possa essere riutilizzato.Sembra che sarebbe pesante.

Ogni pensiero sarebbe apprezzato.Grazie.

È stato utile?

Soluzione

Quando possibile, non dovresti archiviare l'intero contenuto di un file da servire in memoria.Invece, acquisisci un InputStream per i dati e copia i dati nel Servlet OutputStream in pezzi.Per esempio:

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();

Sono d'accordo con Toby, dovresti invece "indirizzarli all'URL S3".

Per quanto riguarda l'eccezione OOM, sei sicuro che abbia a che fare con la fornitura dei dati dell'immagine?Supponiamo che la tua JVM abbia 256 MB di memoria "extra" da utilizzare per servire i dati dell'immagine.Con l'aiuto di Google, "256 MB / 200 KB" = 1310.Per 2 GB di memoria "extra" (al giorno d'oggi una quantità molto ragionevole) potrebbero essere supportati oltre 10.000 client simultanei.Anche così, 1300 client simultanei sono un numero piuttosto elevato.È questo il tipo di carico che hai riscontrato?In caso contrario, potrebbe essere necessario cercare altrove la causa dell'eccezione OOM.

Modifica - Riguardo a:

In questo caso d'uso le immagini possono contenere dati sensibili...

Quando ho letto la documentazione di S3 qualche settimana fa, ho notato che è possibile generare chiavi con scadenza temporale che possono essere allegate agli URL S3.Quindi, non dovresti aprire al pubblico i file su S3.La mia comprensione della tecnica è:

  1. La pagina HTML iniziale contiene collegamenti per il download alla tua webapp
  2. L'utente fa clic su un collegamento per il download
  3. La tua webapp genera un URL S3 che include una chiave che scade, diciamo, entro 5 minuti.
  4. Invia un reindirizzamento HTTP al client con l'URL del passaggio 3.
  5. L'utente scarica il file da S3.Funziona anche se il download impiega più di 5 minuti: una volta avviato, il download può continuare fino al completamento.

Altri suggerimenti

Perché non dovresti semplicemente indirizzarli all'URL S3?Prendere un artefatto da S3 e poi trasmetterlo in streaming attraverso il tuo server vanifica lo scopo dell'utilizzo di S3, che è quello di scaricare la larghezza di banda e l'elaborazione della fornitura delle immagini ad Amazon.

Ho visto molto codice come la risposta di john-vasilef (attualmente accettata), un ciclo while stretto che legge blocchi da un flusso e li scrive nell'altro flusso.

L'argomentazione che vorrei sollevare è contro la duplicazione inutile del codice, a favore dell'utilizzo di IOUtils di Apache.Se lo stai già utilizzando altrove, o se un'altra libreria o framework che stai utilizzando dipende già da esso, è una singola riga conosciuta e ben testata.

Nel codice seguente, eseguo lo streaming di un oggetto da Amazon S3 al client in un servlet.

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 linee di uno schema ben definito con una corretta chiusura del flusso sembrano piuttosto solide.

Sono assolutamente d'accordo sia con Toby che con John Vasileff: S3 è ottimo per scaricare oggetti multimediali di grandi dimensioni se riesci a tollerare i problemi associati.(Un'istanza della propria app lo fa per FLV e MP4 da 10-1000 MB.) Ad esempio:Tuttavia, nessuna richiesta parziale (intestazione dell'intervallo di byte).Bisogna gestirlo "manualmente", tempi di inattività occasionali, ecc.

Se questa non è un'opzione, il codice di John sembra valido.Ho scoperto che un buffer di byte di FILEBUFFERSIZE da 2k è il più efficiente nei segni di microbench.Un'altra opzione potrebbe essere un FileChannel condiviso.(I FileChannel sono thread-safe.)

Detto questo, aggiungerei anche che indovinare cosa ha causato un errore di memoria insufficiente è un classico errore di ottimizzazione.Miglioreresti le tue possibilità di successo lavorando con parametri difficili.

  1. Inserisci -XX:+HeapDumpOnOutOfMemoryError nei parametri di avvio della JVM, per ogni evenienza
  2. utilizzare jmap sulla JVM in esecuzione (jmap -histo <pid>) sotto carico
  3. Analizza le metriche (jmap -histo out put o dai un'occhiata al tuo heap dump).Potrebbe benissimo essere che la tua memoria insufficiente provenga da qualche parte inaspettata.

Ovviamente ci sono altri strumenti là fuori, ma jmap e jhat sono forniti con Java 5+ "pronto all'uso"

Ho preso in considerazione la possibilità di scrivere il file su un'unità temporanea locale e quindi di generare un altro thread per gestire lo streaming in modo che il thread servlet Tomcat possa essere riutilizzato.Sembra che sarebbe pesante.

Ah, non penso che tu non possa farlo.E anche se potessi, sembra dubbio.Il thread Tomcat che gestisce la connessione deve avere il controllo.Se si riscontra una carenza di thread, aumentare il numero di thread disponibili in ./conf/server.xml.Ancora una volta, le metriche sono il modo per rilevarlo: non limitarti a indovinare.

Domanda:Utilizzi anche EC2?Quali sono i parametri di avvio della JVM di Tomcat?

toby ha ragione, dovresti puntare direttamente a S3, se puoi.Se non puoi, la domanda è un po' vaga per dare una risposta precisa:Quanto è grande il tuo heap Java?Quanti flussi sono aperti contemporaneamente quando si esaurisce la memoria?
Quanto è grande il tuo buffer di lettura/scrittura (8K va bene)?
Stai leggendo 8K dallo stream, quindi scrivi 8K sull'output, giusto?Non stai tentando di leggere l'intera immagine da S3, memorizzarla nel buffer e quindi inviare il tutto in una volta?

Se usi buffer da 8K, potresti avere 1000 flussi simultanei in ~ 8Meg di spazio heap, quindi stai sicuramente facendo qualcosa di sbagliato....

A proposito, non ho scelto 8K dal nulla, è la dimensione predefinita per i buffer dei socket, invia più dati, diciamo 1Meg, e bloccherai sullo stack tcp/ip che contiene una grande quantità di memoria.

Devi verificare due cose:

  • Stai chiudendo lo streaming?Molto importante
  • Forse stai offrendo connessioni di streaming "gratuitamente".Il flusso non è grande, ma molti flussi contemporaneamente possono rubarti tutta la memoria.Crea un pool in modo che non sia possibile avere un certo numero di stream in esecuzione contemporaneamente

Oltre a quanto suggerito da John, dovresti svuotare ripetutamente il flusso di output.A seconda del contenitore web, è possibile che memorizzi nella cache parti o addirittura tutto l'output e lo scarichi contemporaneamente (ad esempio, per calcolare l'intestazione Content-Length).Brucerebbe un bel po' di memoria.

Se riesci a strutturare i tuoi file in modo che i file statici siano separati e nel proprio bucket, le prestazioni più veloci oggi possono probabilmente essere ottenute utilizzando la CDN Amazon S3, CloudFront.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top