web-dev-qa-db-ja.com

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

アップストリームサービス(Azure Blobサービス)を呼び出してデータをOutputStreamにプッシュする必要があります。次に、それを反転させて、それをクライアントにプッシュし、akkaを介してクライアントに戻す必要があります。 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);を呼び出すことによって出力ストリームが入力されるように、上流のサービスを呼び出すことです。

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

どうやってこれを行うのですか?

9
MeBigFatGuy

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

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

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

Javaコードの形式が正しくない-ScalaでAkkaを使用しているため、これはすべて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());
2
Alec

この場合のOutputStreamSourceの「実体化された値」であり、ストリームが実行された(または実行中のストリームに「実体化された」)場合にのみ作成されます。 SourceをAkka 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
johanandren