44

スケーリングが必要な Java サーバーを構築しています。サーブレットの 1 つは、Amazon S3 に保存されているイメージを提供します。

最近、負荷がかかっていて、VM のメモリが不足しました。それは、画像を提供するコードを追加した後でした。そのため、より大きなサーブレット レスポンスのストリーミングが問題を引き起こしていると確信しています。

私の質問は次のとおりです。Java サーブレットをコーディングして、データベースやその他のクラウド ストレージから読み取ったときに大きな (>200k) 応答をブラウザーにストリーミングする方法について、ベスト プラクティスはありますか?

ファイルをローカルの一時ドライブに書き込み、別のスレッドを生成してストリーミングを処理し、Tomcat サーブレット スレッドを再利用できるようにすることを検討しました。これは重くなりそうです。

任意の考えをいただければ幸いです。ありがとう。

4

8 に答える 8

58

可能であれば、提供するファイルの内容全体をメモリに保存しないでください。代わりに、データの 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();

私は toby に同意します。代わりに、「S3 URL を参照する」必要があります。

OOM の例外についてですが、それは画像データの提供に関係していますか? JVM に、画像データの提供に使用する 256MB の「追加」メモリがあるとします。Google の助けを借りると、「256MB / 200KB」 = 1310 です。2GB の「追加」メモリ (最近では非常に妥当な量) の場合、10,000 を超える同時クライアントをサポートできます。それでも、1300 の同時クライアントはかなりの数です。これはあなたが経験した負荷のタイプですか?そうでない場合は、OOM 例外の原因を別の場所で探す必要があります。

編集 - について:

このユースケースでは、画像に機密データが含まれる可能性があります...

数週間前に S3 のドキュメントを読んだとき、S3 の URL に添付できる有効期限のあるキーを生成できることに気付きました。したがって、S3 上のファイルを公開する必要はありません。テクニックの私の理解は次のとおりです。

  1. 最初の HTML ページには、Web アプリケーションへのダウンロード リンクがあります
  2. ユーザーがダウンロード リンクをクリックする
  3. Web アプリケーションは、たとえば 5 分で有効期限が切れるキーを含む S3 URL を生成します。
  4. 手順 3 の URL を使用して HTTP リダイレクトをクライアントに送信します。
  5. ユーザーは S3 からファイルをダウンロードします。これは、ダウンロードに 5 分以上かかる場合でも機能します。ダウンロードが開始されると、完了まで続行できます。
于 2008-09-11T03:53:45.523 に答える
17

S3 の URL を指定しないのはなぜですか? S3 からアーティファクトを取得し、独自のサーバーを介してストリーミングすることは、Amazon に画像を提供するための帯域幅と処理をオフロードするという S3 を使用する目的を無効にします。

于 2008-09-11T02:45:02.617 に答える
11

john-vasilef の (現在受け入れられている) 回答のような多くのコードを見てきました。一方のストリームからチャンクを読み取り、もう一方のストリームに書き込むタイトな while ループです。

私が主張したいのは、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 行は、かなりしっかりしているように見えます。

于 2014-04-23T18:31:36.330 に答える
2

toby の言うとおりです。可能であれば、S3 を直接指定する必要があります。それができない場合、質問は正確な回答を与えるには少しあいまいです: Java ヒープはどのくらいの大きさですか? メモリが不足したときに同時に開いているストリームの数は?
読み書き/バッファの大きさはどのくらいですか (8K が適切です)。
ストリームから 8K を読み取り、8K を出力に書き込みますよね? S3 からイメージ全体を読み込んでメモリにバッファリングし、一度にすべてを送信しようとしていませんか?

8K バッファを使用する場合、1000 個の同時ストリームが最大 8 メガバイトのヒープ スペースに入る可能性があるため、間違いなく何か間違ったことをしています....

ところで、私は何もないところから 8K を選びませんでした。これはソケット バッファのデフォルト サイズであり、1Meg などのより多くのデータを送信すると、大量のメモリを保持する tcp/ip スタックでブロックされます。

于 2008-09-11T03:38:08.917 に答える
2

toby と John Vasileff の両方に強く同意します。関連する問題を許容できるのであれば、S3 は大きなメディア オブジェクトのオフロードに最適です。(独自のアプリのインスタンスは、10 ~ 1000 MB の FLV および MP4 に対してこれを行います。) 例: ただし、部分的な要求 (バイト範囲ヘッダー) はありません。その「手動」、時折のダウンタイムなどを処理する必要があります.

それがオプションでない場合、ジョンのコードは良さそうです。2k の FILEBUFFERSIZE のバイト バッファーがマイクロベンチ マークで最も効率的であることがわかりました。もう 1 つのオプションは、共有 FileChannel です。(FileChannel はスレッドセーフです。)

とはいえ、メモリ不足エラーの原因を推測することは、古典的な最適化の間違いであることも付け加えておきます。ハードメトリックを使用することで、成功の可能性が向上します。

  1. 念のため、-XX:+HeapDumpOnOutOfMemoryError を JVM 起動パラメータに配置します。
  2. 実行中の JVM で jmap を使用する ( jmap -histo <pid> ) 負荷がかかっている
  3. メトリックを分析します (jmap -histo 出力、または jhat でヒープ ダンプを調べます)。あなたの記憶喪失は、予期せぬところから来ている可能性が非常に高い.

もちろん、他にもツールはありますが、jmap と jhat には Java 5+ が「そのまま」付属しています。

ファイルをローカルの一時ドライブに書き込み、別のスレッドを生成してストリーミングを処理し、Tomcat サーブレット スレッドを再利用できるようにすることを検討しました。これは重くなりそうです。

ああ、私はあなたがそれをすることができないとは思わない。できたとしても、疑わしいように聞こえます。接続を管理している tomcat スレッドが制御下にある必要があります。スレッド不足が発生している場合は、./conf/server.xml で使用可能なスレッドの数を増やしてください。繰り返しになりますが、メトリクスはこれを検出する方法です。推測だけではいけません。

質問: EC2 でも実行していますか? Tomcat の JVM 起動パラメータは何ですか?

于 2008-09-11T06:24:24.727 に答える
0

静的ファイルが個別に独自のバケットに配置されるようにファイルを構成できる場合、Amazon S3 CDN であるCloudFrontを使用することで、現時点で最速のパフォーマンスを達成できる可能性があります。

于 2009-10-01T15:23:14.750 に答える
0

次の 2 点を確認する必要があります。

  • ストリームを閉じますか?非常に重要
  • ストリーム接続を「無料」で提供しているのかもしれません。ストリームは大きくありませんが、同時に多くのストリームがすべてのメモリを盗む可能性があります。特定の数のストリームを同時に実行できないようにプールを作成する
于 2008-09-11T03:23:16.673 に答える
0

John が提案したことに加えて、出力ストリームを繰り返しフラッシュする必要があります。Web コンテナーによっては、出力の一部またはすべてをキャッシュし、一度にフラッシュすることができます (たとえば、Content-Length ヘッダーを計算するため)。それはかなりのメモリを消費します。

于 2008-09-11T06:16:03.170 に答える