Akkaストリームをアップストリームサービスに渡して入力する


9

アップストリームサービス(Azure Blobサービス)を呼び出して、データをOutputStreamにプッシュする必要があります。次に、それを反転させて、それをクライアントにプッシュする必要があります。akkaがなければ(そしてサーブレットコードだけ)、ServletOutputStreamを取得して、それをazureサービスのメソッドに渡します。

私がつまずくことを試みることができる最も近い、そして明らかにこれは間違っている、このようなものです

        Source<ByteString, OutputStream> source = StreamConverters.asOutputStream().mapMaterializedValue(os -> {
            blobClient.download(os);
            return os;
        });

        ResponseEntity resposeEntity = HttpEntities.create(ContentTypes.APPLICATION_OCTET_STREAM, preAuthData.getFileSize(), source);

        sender().tell(new RequestResult(resposeEntity, StatusCodes.OK), self());

アイデアは、blobClient.download(os);を呼び出すことによって出力ストリームが入力されるように、上流のサービスを呼び出すことです。

ラムダ関数が呼び出されて返ってくるように見えますが、その後データや何かがないために失敗します。そのラムダ関数が機能するようになっているはずがないように見えますが、おそらく機能するオブジェクトを返しますか?わからない。

これはどのように行うのですか?


の動作はdownload何ですか?データがストリームosされ、データの書き込みが完了して初めて返されますか?
アレック

回答:


2

ここでの本当の問題は、Azure APIがバックプレッシャー用に設計されていないことです。出力ストリームがデータを追加する準備ができていないことをAzureに通知する方法はありません。別の言い方をすれば、Azureがデータを消費するよりも速くデータをプッシュする場合、どこかに醜いバッファオーバーフローエラーが発生する必要があります。

この事実を受け入れて、私たちにできる次善の策は次のとおりです。

  • Source.lazySourceダウンストリームの要求があるときにのみデータのダウンロードを開始するために使用します(別名、ソースが実行されており、データが要求されています)。
  • downloadソースが返されないようにブロックせずに実行を継続できるように、呼び出しを他のスレッドに配置します。これを行う方法の1つは、Future(Javaのベストプラクティスが何かはわかりませんが、どちらの方法でも問題なく動作するはずです)を使用することです。最初は問題ではありませんが、実行コンテキストを選択する必要があるかもしれませんsystem.dispatcher-それはすべてdownloadがブロックされているかどうかに依存します。

このJavaコードが不正な形式である場合は、事前に謝罪します。AkkaをScalaで使用しているため、これはすべてAkka Java APIとJava構文リファレンスを参照することです。

ResponseEntity responseEntity = HttpEntities.create(
  ContentTypes.APPLICATION_OCTET_STREAM,
  preAuthData.getFileSize(),

  // Wait until there is downstream demand to intialize the source...
  Source.lazySource(() -> {
    // Pre-materialize the outputstream before the source starts running
    Pair<OutputStream, Source<ByteString, NotUsed>> pair =
      StreamConverters.asOutputStream().preMaterialize(system);

    // Start writing into the download stream in a separate thread
    Futures.future(() -> { blobClient.download(pair.first()); return pair.first(); }, system.getDispatcher());

    // Return the source - it should start running since `lazySource` indicated demand
    return pair.second();
  })
);

sender().tell(new RequestResult(responseEntity, StatusCodes.OK), self());

素晴らしい。どうもありがとう。あなたの例の小さな編集は次のとおりです:Futures.future(()-> {blobClient.download(pair.first()); return pair.first();}、system.getDispatcher());
MeBigFatGuy

@MeBigFatGuyそうですね、ありがとう!
アレック

1

OutputStreamこの場合の「値をマテリアライズ」であるSourceと、それはストリームのみが実行されると、作成した(または実行中のストリームに「マテリア」)されます。SourceAkka HTTPにを渡して、後で実際にソースを実行するため、実行は制御できません。

.mapMaterializedValue(matval -> ...)通常、マテリアライズされた値を変換するために使用されますが、マテリアライズの一部として呼び出されるため、それを使用して、メッセージでmatvalを送信するなどの副作用を実行できます。ファンキーに見えても。ラムダが完了するまで、ストリームは実体化を完了せず、実行状態になることを理解することが重要です。これはdownload()、別のスレッドで一部の作業を分岐してすぐに戻るのではなく、ブロックしている場合に問題が発生することを意味します。

ただし、別の解決策があります。これはSource.preMaterialize()、ソースを具体化しPair、具体化された値とSource、すでに開始されているソースを使用するために使用できる新しい値を提供します。

Pair<OutputStream, Source<ByteString, NotUsed>> pair = 
  StreamConverters.asOutputStream().preMaterialize(system);
OutputStream os = pair.first();
Source<ByteString, NotUsed> source = pair.second();

コードでいくつかの追加の考慮事項があることに注意してください。最も重要なのは、blobClient.download(os)呼び出しが完了するまでブロックされ、それをアクターから呼び出す場合です。その場合、アクターがディスパッチャーを枯渇させて停止しないようにする必要があります。アプリケーション内の他のアクターが実行されないようにする(Akkaのドキュメントを参照:https : //doc.akka.io/docs/akka/current/typed/dispatchers.html#blocking-needs-careful-management)。


1
ご回答ありがとうございます。これがどのように機能するかわかりませんか?blobClient.download(os)が呼び出されたとき、バイトはどこに行きますか(自分で呼び出している場合)?書き込まれるのを待っているテラバイトのデータがあると想像してください。これは基本的にはIOUtils.copyのような操作になるように、blobClient.download呼び出しをsender.tell呼び出しから呼び出さなければならないようです。preMaterializeを使用すると、それがどのように行われるかわかりませんか?
MeBigFatGuy

OutputStreamには内部バッファーがあり、そのバッファーがいっぱいになるまで書き込みの受け入れを開始します。非同期ダウンストリームが要素の消費を開始していなければ、書き込みスレッドをブロックします(そのため、ブロックを処理することが重要であると述べました)。
johanandren

1
しかし、私がpreMaterializeしてOutputStreamを取得した場合、blobClient.download(os);を実行しているのは私のコードです。正しい?つまり、続行する前に完了する必要があります。これは不可能です。
MeBigFatGuy

download(os)がスレッドをフォークしない場合は、スレッドがブロックされていることに対処し、それによって他の操作が停止しないようにする必要があります。1つの方法は、スレッドをフォークして作業を行う方法です。別の方法は、最初にアクターから応答してからブロック作業を行う方法です。その場合、アクターが他のアクターを枯渇させないようにする必要があります。私の答え。
johanandren

この時点で、私はそれをまったく機能させようとしているだけです。10バイトのファイルも処理できません。
MeBigFatGuy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.