bugfix> java > 投稿

私が達成しようとしているのは、@ AsyncとCompletableFutureを使用してパフォーマンスを向上させ、この簡単な方法でマルチスレッドを使用してRESTApiのコントローラーを作成できるかどうかです。

これが私がしていることです、これが私のコントローラーです:

@PostMapping("/store")
@Async
public CompletableFuture<ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>>> saveNewCategoryBPSJ(@Valid @RequestBody InputRequest<CategoryBPSJRequestDto> request) {
    
    CompletableFuture<ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>>> future = new CompletableFuture<>();
    future.complete(ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJService.save(request))));
    return future;
}

VS

@PostMapping("/store")
public ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>> saveNewCategoryBPSJ(@Valid @RequestBody InputRequest<CategoryBPSJRequestDto> request) {
    
    return ResponseEntity.ok(new ResponseRequest<>("okay", categoryBPSJService.save(request));
}

最初のコントローラー関数でわかるように、関数の応答にCompletableFutureを追加しますが、サービスでは、この行に保存します categoryBPSJService.save(request) 非同期ではなく、次のような単純な関数です。

public CategoryBpsjResponseDto save(InputRequest<CategoryBPSJRequestDto> request) {
    CategoryBPSJRequestDto categoryBPSJDto = request.getObject();
    Boolean result = categoryBPSJRepository.existsCategoryBPSJBycategoryBPSJName(categoryBPSJDto.getCategoryBPSJName());
    if(result){
        throw new ResourceAlreadyExistException("Category BPSJ "+ categoryBPSJDto.getCategoryBPSJName() + " already exists!");
    }
    CategoryBPSJ categoryBPSJ = new CategoryBPSJ();
    categoryBPSJ = map.DTOEntity(categoryBPSJDto);
    categoryBPSJ.setId(0L);
    categoryBPSJ.setIsDeleted(false);
    CategoryBPSJ newCategoryBPSJ = categoryBPSJRepository.save(categoryBPSJ);
    
    CategoryBpsjResponseDto categoryBpsjResponseDto = map.entityToDto(newCategoryBPSJ);
    return categoryBpsjResponseDto;
}

JPA接続で単純なオブジェクトを返すだけです。これにより、リクエストのパフォーマンスが向上しますか?または私はそれを増やすために何かが欠けていますか?または、コントローラーにCompletableFutureと@Asyncがある場合とない場合で違いはありませんか?

*注:私のプロジェクトはJava13に基づいています

回答 1 件
  • CompletableFutureを使用しても、サーバーのパフォーマンスが魔法のように向上することはありません。

    通常JettyまたはTomcat上にあるサーブレットAPI上に構築されたSpringMVCを使用している場合、リクエストごとに1つのスレッドがあります。これらのスレッドが取得されるプールは通常かなり大きいので、かなりの量の同時要求を持つことができます。ここでは、リクエストスレッドのブロックは問題ではありません。このスレッドはとにかくその単一のリクエストのみを処理するため、他のリクエストはブロックされません(プールに使用可能なスレッドがない場合を除く)。つまり、IOできるブロックしている、あなたのコードできる同期する。

    ただし、Spring WebFluxを使用している場合、通常はNetty上で、リクエストはメッセージ/イベントとして処理されます。1つのスレッドで複数のリクエストを処理できるため、プールのサイズを減らすことができます(スレッドは高価です)。この場合、スレッドでブロックしますですIOが終了するのを待っている他のリクエストにつながる可能性があるため、問題が発生します。つまり、IOしなければならないノンブロッキング、あなたのコードしなければならない非同期であるため、操作が終了するのを単にアイドル状態で待つのではなく、スレッドを解放して「その間に」別の要求を処理できます。参考までに、このリアクティブスタックは魅力的に見えますが、コードベースの非同期性のため、注意すべき他の多くの欠点があります。

    JPAはJDBC(IOでブロックする)に依存しているため、ブロックしています。つまり、Spring WebFluxでJPAを使用することはあまり意味がなく、「要求スレッドをブロックしない」という原則に反するため、避ける必要があります。回避策(たとえば、別のスレッドプール内からSQLクエリを実行する)を見つけましたが、これは根本的な問題を実際には解決しません。IOがブロックされ、競合が発生する可能性があります。人々は、Java用の非同期SQLドライバー(Spring Data R2DBCや基盤となるベンダー固有のドライバーなど)に取り組んでいます。これは、たとえばWebFluxコードベース内から使用できます。オラクルは独自の非同期ドライバーであるADBAにも取り組み始めましたが、彼らはに焦点を当てたためにプロジェクトを放棄しました繊維Project Loom経由(Javaでの並行性の処理方法がまもなく完全に変わる可能性があります)。

    Spring MVCを使用しているようです。つまり、リクエストごとのスレッドモデルに依存しています。 CompletableFutureをコードにドロップするだけでは、状況は改善されません。すべてのサービスレイヤーロジックをデフォルトのリクエストスレッドプールとは別のスレッドプールに委任するとします。リクエストスレッドは利用可能になりますが、競合は他のスレッドプールで発生します。つまり、問題を移動するだけです。周り。

    場合によっては、別のプールに延期することも興味深い場合があります。計算量の多い操作(パスフレーズハッシュなど)、または多くの(ブロック)IOをトリガーする特定のアクションなどですが、競合が発生する可能性があることに注意してください。つまり、リクエストはブロック/待機される可能性があります。

    もし、あんたが行うコードベースのパフォーマンスの問題を観察し、最初にプロファイルします。 YourKit(他の多くが利用可能)のようなツール、またはNewRelic(他の多くも利用可能)のようなAPMを使用します。ボトルネックがどこにあるかを理解し、最悪の事態を修正し、繰り返します。そうは言っても、いくつかの通常の容疑者:IOが多すぎます(特にJPAの場合、たとえばn +1を選択)、シリアル化/逆シリアル化が多すぎます(特に、JPAの場合、熱心なフェッチなど)。基本的に、JPAはインクルード通常の容疑者:これは強力なツールですが、設定を誤るのは非常に簡単です。SQLだと思うそれを正しくするために私見。開発時に生成されたSQLクエリをログに記録することを強くお勧めします。驚かれるかもしれません。 Vlad Mihalceaのブログは、JPA関連の優れたリソースです。興味深い読み物:MartinFowlerによるOrmHate。


    特定のコードスニペットに関して、SpringなしでバニラJavaを使用するとします。 @Async サポート:

    CompletableFuture<ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>>> future = new CompletableFuture<>();
    future.complete(ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJService.save(request))));
    return future;
    

    これはしません categoryBPSJService.save(request) 非同期で実行します。コードを少し分割すると、より明確になります。

    CategoryBpsjResponseDto categoryBPSJ = categoryBPSJService.save(request)
    CompletableFuture<ResponseEntity<ResponseRequest<CategoryBpsjResponseDto>>> future = new CompletableFuture<>();
    future.complete(ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJ)));
    return future;
    
    

    ここで何が起こったのか分かりますか? categoryBPSJ 同期的に呼び出され、結果を保持する完了済みのフューチャーを作成します。ここで本当にCompletableFutureを使用したい場合は、サプライヤーを使用する必要があります。

    CompletableFuture<CategoryBpsjResponseDto> future = CompletableFuture.supplyAsync(
        () -> categoryBPSJService.save(request),
        someExecutor
    );
    return future.thenApply(categoryBPSJ -> ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJ)));
    
    

    春の @Async 基本的には上記の単なるシンタックスシュガーです。どちらかまたは両方を使用してください。技術的なAOP /プロキシの理由から、注釈付きのメソッド @Async 確かにCompletableFutureを返す必要があります。その場合、すでに完了したfutureを返すことは問題ありません。Springはとにかくそれをエグゼキュータで実行します。通常、サービスレイヤーは「非同期」のレイヤーですが、コントローラーは、返された将来にわたって消費して構成するだけです。

    CompletableFuture<CategoryBpsjResponseDto> = categoryBPSJService.save(request);
    return future.thenApply(categoryBPSJ -> ResponseEntity.ok(new ResponseRequest<>("Okay", categoryBPSJ)));
    
    

    コードをデバッグして、すべてが期待どおりに動作することを確認します。IDEは、ブレークポイントによって現在ブロックされているスレッドを表示します。


    補足:これは、ブロッキングと非ブロッキング、MVCとWebFlux、同期と非同期などから理解したことの簡略化された要約です。これは非常に表面的なものであり、私のポイントの一部は100%真実であるほど具体的ではない可能性があります。

あなたの答え