1. 序章

このチュートリアルでは、SpringMVCの@Async アノテーションについて説明し、次にSpringWebFluxについて理解します。 私たちの目標は、これら2つの違いをよりよく理解することです。

2. 実装シナリオ

ここでは、これらの各APIを使用して単純なWebアプリケーションを実装する方法を示すシナリオを選択します。 さらに、スレッド管理と、それぞれの場合のブロッキングまたは非ブロッキングI/Oについて詳しく知ることに特に関心があります。

文字列の結果を返すエンドポイントが1つあるWebアプリケーションを選択しましょう。 ここでのポイントは、リクエストが Filter をわずか200msの遅延で通過し、Controllerが計算して結果を返すのに500msを必要とすることです。

次に、両方のエンドポイントで Apache ab を使用して負荷をシミュレートし、JConsoleを使用してアプリの動作を監視します。

この記事で言及する価値があるかもしれませんが、私たちの目標はこれら2つのAPI間のベンチマークではなく、スレッド管理を追跡できるようにするための小さな負荷テストです

3. SpringMVC非同期

Spring 3.0では、@Asyncアノテーションが導入されました。 @Async の目標は、アプリケーションが別のスレッドで高負荷のジョブを実行できるようにすることです。 また、発信者は興味があれば結果を待つことができます。 したがって、戻りタイプは void であってはならず、 Future CompleteableFuture 、またはListenableFutureのいずれでもかまいません。

さらに、 Spring 3.2 は、 org.springframework.web.context.request.async パッケージを導入しました。これは、サーブレット3.0 とともに、非同期プロセスの喜びをもたらします。 Webレイヤーに。 したがって、Spring 3.2以降、 @Async は、@Controllerまたは@RestControllerと注釈が付けられたクラスで使用できます。

クライアントがリクエストを開始すると、 DispatcherServlet インスタンスに到達するまで、フィルターチェーン内の一致するすべてのフィルターを通過します。

次に、サーブレットがリクエストの非同期ディスパッチを処理します。 AsyncWebRequest#startAsyncを呼び出して要求を開始済みとしてマークし、は要求処理を WebSyncManager のインスタンスに転送し、応答をコミットせずにジョブを終了します。 フィルタチェーンもルートに対して逆方向にトラバースされます。

WebAsyncManager は、関連付けられたExecutorServiceで要求処理ジョブを送信します。 結果の準備ができると、クライアントに応答を返すようにDispatcherServletに通知します。

4. SpringAsyncの実装

アプリケーションクラスAsyncVsWebFluxAppを記述して実装を開始しましょう。 H ere、@ EnableAsync は、SpringBootアプリケーションの非同期を有効にする魔法を実行します。

@SpringBootApplication
@EnableAsync
public class AsyncVsWebFluxApp {
    public static void main(String[] args) {
        SpringApplication.run(AsyncVsWebFluxApp.class, args);
    }
}

次に、 AsyncFilter 、を実装します javax.servlet.Filter。 の遅延をシミュレートすることを忘れないでください doFilter 方法:

@Component
public class AsyncFilter implements Filter {
    ...
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
      throws IOException, ServletException {
        // sleep for 200ms 
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

最後に、「 /async_result」エンドポイントを使用してAsyncControllerを開発します。

@RestController
public class AsyncController {
    @GetMapping("/async_result")
    @Async
    public CompletableFuture getResultAsyc(HttpServletRequest request) {
        // sleep for 500 ms
        return CompletableFuture.completedFuture("Result is ready!");
    }
}

getResultAsyncの上の@Asyncのため、このメソッドは、アプリケーションのデフォルトのExecutorServiceの別のスレッドで実行されます。 ただし、メソッドに対して特定のExecutorServiceを設定することは可能です。

テスト時間! アプリケーションを実行し、Apache ab、またはその他のツールをインストールして負荷をシミュレートしましょう。 次に、「async_result」エンドポイントを介して一連の同時リクエストを送信できます。 JConsoleを実行し、それをJavaアプリケーションにアタッチして、プロセスを監視できます。

ab -n 1600 -c 40 localhost:8080/async_result

 

5. 春のWebFlux

Spring 5.0は、 WebFluxを導入して、リアクティブWebを非ブロッキング方式でサポートします。 WebFluxはreactorAPIに基づいており、リアクティブストリームのもう1つのすばらしい実装です。

Spring WebFluxは、リアクティブバックプレッシャとサーブレット3.1+を非ブロッキングI/Oでサポートします。 したがって、Netty、Undertow、Jetty、Tomcat、またはサーブレット3.1以降と互換性のあるサーバーで実行できます。

すべてのサーバーが同じスレッド管理と同時実行制御モデルを使用しているわけではありませんが、Spring WebFluxは、ノンブロッキングI/Oとリアクティブバックプレッシャをサポートしている限り正常に動作します。

Spring WebFluxを使用すると、 Mono、Flux、とそれらの豊富な演算子セットを使用して、宣言的な方法でロジックを分解できます。 さらに、 @Controller 注釈付きのエンドポイント以外に機能エンドポイントを設定できますが、 SpringMVCでも使用できるようになりました。

6. SpringWebFluxの実装

WebFlux実装の場合、非同期と同じパスを使用します。 それでは、最初にAsyncVsWebFluxAppを作成しましょう。

@SpringBootApplication
public class AsyncVsWebFluxApp {
    public static void main(String[] args) {
        SpringApplication.run(AsyncVsWebFluxApp.class, args);
    }
}

次に、WebFilterを実装するWebFluxFilter を記述しましょう。意図的な遅延を生成してから、要求をフィルターチェーンに渡します。

@Component
public class WebFluxFilter implements org.springframework.web.server.WebFilter {

    @Override
    public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
        return Mono
          .delay(Duration.ofMillis(200))
          .then(
            webFilterChain.filter(serverWebExchange)
          );
    }
}

最後に、WebFluxControllerがあります。 と呼ばれるエンドポイントを公開します 「/flux_result」 とを返します単核症応答として:

@RestController
public class WebFluxController {

    @GetMapping("/flux_result")
    public Mono getResult(ServerHttpRequest request) {
       return Mono.defer(() -> Mono.just("Result is ready!"))
         .delaySubscription(Duration.ofMillis(500));
    }
}

テストでは、非同期サンプルアプリケーションと同じアプローチを採用しています。 結果のサンプルは次のとおりです。

ab -n 1600 -c 40 localhost:8080/flux_result

7. 違いは何ですか?

SpringAsyncはサーブレット3.0仕様をサポートしていますが、SpringWebFluxはサーブレット3.1以降をサポートしています。 それは多くの違いをもたらします:

  • クライアントとの通信中のSpringAsyncI/Oモデルがブロックされています。 遅いクライアントではパフォーマンスの問題が発生する可能性があります。 一方、SpringWebFluxはノンブロッキングI/Oモデルを提供します。
  • Spring Asyncではリクエスト本文またはリクエストパーツの読み取りがブロックされていますが、SpringWebFluxではブロックされていません。
  • Spring Asyncでは、FilterServletが同期的に動作していますが、SpringWebFluxは完全な非同期通信をサポートしています。
  • Spring WebFluxは、NettyやUndertowなど、SpringAsyncよりも幅広いWeb/アプリケーションサーバーと互換性があります。

さらに、Spring WebFluxはリアクティブバックプレッシャをサポートしているため、Spring MVCAsyncとSpringMVCの両方よりも、高速プロデューサーにどのように対応するかをより細かく制御できます。

Spring Fluxは、その背後にあるReactor APIのおかげで、関数型コーディングスタイルと宣言型API分解への明確なシフトも持っています。

これらすべてのアイテムにより、Spring WebFluxを使用できるようになりますか? ええと、 SpringAsyncまたはSpringMVCでさえ、システムの望ましい負荷スケーラビリティまたは可用性に応じて、そこにある多くのプロジェクトに対する正しい答えかもしれません

スケーラビリティに関しては、SpringAsyncは同期SpringMVC実装よりも優れた結果をもたらします。 Spring WebFluxは、その反応性により、弾力性と高い可用性を提供します。

8. 結論

この記事では、SpringAsyncとSpringWebFluxについて詳しく学び、基本的な負荷テストを使用して理論的および実際的にそれらを比較しました。

いつものように、非同期サンプルWebFluxサンプルの完全なコードはGitHubから入手できます。