概要

Spring WebFlux が作成されたのはなぜですか?

答えの一部は、少ないスレッドで同時実行を処理し、より少ないハードウェアリソースで拡張するためのノンブロッキング Web スタックの必要性です。サーブレットのノンブロッキング I/O は、契約が同期(FilterServlet)またはブロッキング(getParametergetPart)であるサーブレット API の残りの部分から離れます。これが、新しい共通 API がノンブロッキングランタイム全体の基盤として機能する動機でした。非同期のノンブロッキングスペースで十分に確立されているサーバー(Netty など)があるため、これは重要です。

もう 1 つの理由は関数型プログラミングです。Java 5 にアノテーションが追加されたことで新たな可能性(アノテーション付き REST コントローラーや単体テストなど)を切り開いたように、Java 8 にラムダ式を追加することにより Java の関数 API の可能性を開きました。これは、非同期ロジックの宣言的な合成を可能にする、ノンブロッキングアプリケーションと継続渡しスタイル API(CompletableFuture および ReactiveX (英語) によって普及した)の恩恵です。プログラミングモデルレベルでは、Java 8 により Spring WebFlux はアノテーション付きコントローラーとともに関数 Web エンドポイントを提供できるようになりました。

「リアクティブ」を定義する

「ノンブロッキング」と「関数型」に触れましたが、リアクティブとはどういう意味でしょうか?

「リアクティブ」という用語は、変化への反応に基づいて構築されたプログラミングモデルを指します。ネットワークコンポーネントは I/O イベントに反応し、UI コントローラーはマウスイベントに反応します。その意味で、ノンブロッキングはリアクティブです。なぜなら、ブロックされる代わりに、操作が完了するかデータが利用可能になると通知に反応するモードになっているからです。

Spring チームが「リアクティブ」に関連付けるもう 1 つの重要なメカニズムは、ノンブロッキングバックプレッシャーです。同期命令型コードでは、呼び出しをブロックすることは、呼び出し側を待機させるバックプレッシャーの自然な形として機能します。ノンブロッキングコードでは、高速プロデューサーがその宛先を圧倒しないように、イベントのレートを制御することが重要になります。

Reactive Streams は、バックプレッシャーのある非同期コンポーネント間の相互作用を定義する小さな仕様 [GitHub] (英語) (Java 9 でも採用 (標準 Javadoc) )です。たとえば、データリポジトリ(パブリッシャー (英語) として機能)は、HTTP サーバー(サブスクライバー (英語) として機能)がレスポンスに書き込むことができるデータを生成できます。Reactive Streams の主な目的は、サブスクライバーがパブリッシャーがデータを生成する速度と速度を制御できるようにすることです。

よくある質問: パブリッシャーがスローダウンできない場合はどうなるでしょうか?
Reactive Streams の目的は、メカニズムと境界を確立することのみです。パブリッシャーがスローダウンできない場合、バッファーするか、ドロップするか、失敗するかを決定する必要があります。

リアクティブ API

Reactive Streams は相互運用性に重要なロールを果たします。ライブラリやインフラストラクチャコンポーネントにとっては興味深いものですが、アプリケーション API としては低レベルすぎるため、あまり有用ではありません。アプリケーションには、Java 8 Stream API に似ていますが、コレクションだけでなく、非同期ロジックを構成するために、より高レベルでより関数的な API が必要です。これがリアクティブライブラリが果たすロールです。

Reactor [GitHub] (英語) は、Spring WebFlux に最適なリアクティブライブラリです。ReactiveX 演算子のボキャブラリー (英語) に合わせた豊富な演算子セットを通じて、0..1(Mono)および 0..N(Flux)のデータシーケンスで動作する Mono (英語) および Flux (英語) API 型を提供します。Reactor は Reactive Streams ライブラリであるため、すべてのオペレーターがノンブロッキングバックプレッシャーをサポートしています。Reactor は、サーバー側の Java に重点を置いています。Spring との緊密なコラボレーションで開発されました。

WebFlux は、コア依存として Reactor を必要としますが、Reactive Streams を介して他のリアクティブライブラリと相互運用できます。一般的なルールとして、WebFlux API は入力としてプレーン Publisher を受け入れ、それを内部で Reactor 型に適合させ、それを使用して、出力として Flux または Mono を返します。任意の Publisher を入力として渡すことができ、出力に操作を適用できますが、別のリアクティブライブラリで使用するために出力を調整する必要があります。可能な場合はいつでも(たとえば、アノテーション付きコントローラー)、WebFlux は RxJava または別のリアクティブライブラリの使用に透過的に適応します。詳細については、リアクティブライブラリを参照してください。

Reactive API に加えて、WebFlux は Kotlin のコルーチン API でも使用でき、より命令的なスタイルのプログラミングを提供します。次の Kotlin コードサンプルは、Coroutines API で提供されます。

プログラミングモデル

spring-web モジュールには、HTTP 抽象化、サポートされるサーバー用の Reactive Streams アダプターコーデック、サーブレット API に相当するがノンブロッキング契約を備えたコア WebHandler API など、Spring WebFlux の基礎となるリアクティブ基盤が含まれています。

その基盤で、Spring WebFlux は 2 つのプログラミングモデルの選択肢を提供します。

  • アノテーション付きコントローラー : Spring MVC と一致し、spring-web モジュールからの同じアノテーションに基づいています。Spring MVC と WebFlux の両方のコントローラーは、リアクティブ (Reactor および RxJava) の戻り値の型をサポートしているため、区別するのは容易ではありません。注目すべき違いの 1 つは、WebFlux はリアクティブ @RequestBody 引数もサポートしていることです。

  • 関数エンドポイント : ラムダベースの軽量で関数プログラミングモデル。これは、アプリケーションがリクエストのルーティングと処理に使用できる小さなライブラリまたはユーティリティのセットと考えることができます。アノテーション付きコントローラーとの大きな違いは、アプリケーションが開始から終了までのリクエスト処理を担当することと、アノテーションを介してインテントを宣言してコールバックすることです。

適用性

Spring MVC または WebFlux ?

当然の質問ですが、あやふやな二分法を設定する質問です。実際には、両方が連携して、使用可能なオプションの範囲を拡張します。この 2 つは、相互に継続性と一貫性を保つように設計されており、並行して使用できます。また、双方からのフィードバックは双方に利益をもたらします。次の図は、この 2 つがどのように関連しているか、共通しているもの、それぞれが一意にサポートするものを示しています。

spring mvc and webflux venn

次の特定の点を考慮することをお勧めします。

  • 正常に動作する Spring MVC アプリケーションがある場合は、変更する必要はありません。命令型プログラミングは、コードを記述、理解し、デバッグする最も簡単な方法です。歴史的に、ほとんどのライブラリがブロックされているため、ライブラリの選択肢は最大限に広がります。

  • すでにノンブロッキング Web スタックを探している場合、Spring、WebFlux は、この分野の他のスタックと同じ実行モデルの利点を提供し、さらに、サーバー (Netty、Tomcat、Jetty、サーブレットコンテナー)、プログラミングモデル (アノテーション付きコントローラーと機能 Web エンドポイント)、およびリアクティブライブラリ (Reactor、RxJava、その他) の選択肢も提供します。

  • Java 8 ラムダまたは Kotlin で使用する軽量で関数 Web フレームワークに興味がある場合は、Spring WebFlux 関数 Web エンドポイントを使用できます。また、透明性と制御性を高めることでメリットが得られる複雑な要件を持たない小規模なアプリケーションやマイクロサービスにも適しています。

  • マイクロサービスアーキテクチャでは、Spring MVC または Spring WebFlux コントローラーまたは Spring WebFlux 関数エンドポイントのいずれかを備えたアプリケーションを混在させることができます。両方のフレームワークで同じアノテーションベースのプログラミングモデルをサポートすることで、知識の再利用が容易になり、適切なジョブに適切なツールを選択することも容易になります。

  • アプリケーションを評価する簡単な方法は、その依存関係をチェックすることです。使用する永続化 API (JPA、JDBC) またはネットワーク API をブロックしている場合、少なくとも一般的なアーキテクチャには Spring MVC が最適です。Reactor と RxJava の両方で別のスレッドでブロッキングコールを実行することは技術的に可能ですが、ノンブロッキング Web スタックを最大限に活用することはできません。

  • リモートサービスを呼び出す Spring MVC アプリケーションがある場合は、リアクティブ WebClient を試してください。Spring MVC コントローラーメソッドから直接リアクティブ型 (Reactor、RxJava、またはその他) を返すことができます。 1 回の呼び出しあたりの待機時間または呼び出し間の相互依存性が大きいほど、メリットは大きくなります。Spring MVC コントローラーは、他のリアクティブコンポーネントも呼び出すことができます。

  • 大規模なチームがある場合は、ノンブロッキング、関数、宣言的プログラミングへの移行における急な学習曲線に留意してください。完全なスイッチなしで開始する実用的な方法は、リアクティブ WebClient を使用することです。それを超えて、小さく始めて、利益を測定します。幅広いアプリケーションでは、シフトは不要であると予想されます。どのような利点を探すべきかわからない場合は、ノンブロッキング I/O の仕組み(たとえば、シングルスレッド Node.js の同時実行)とその影響について学習することから始めてください。

サーバー

Spring、WebFlux は、Tomcat、Jetty、サーブレットコンテナー、Netty などの非サーブレットランタイムでもサポートされています。すべてのサーバーは低レベルの共通 API に準拠しているため、サーバー間で高レベルのプログラミングモデルをサポートできます。

Spring WebFlux には、サーバーを起動または停止するためのサポートが組み込まれていません。ただし、Spring 構成と WebFlux インフラストラクチャからアプリケーションをアセンブルし、数行のコードで実行するは簡単です。

Spring Boot には、これらの手順を自動化する WebFlux スターターがあります。デフォルトではスターターは Netty を使用しますが、Maven または Gradle の依存関係を変更することで、Tomcat または Jetty に簡単に切り替えることができます。Spring Boot のデフォルトは Netty です。これは、非同期かつノンブロッキングな環境でより広く使用されており、クライアントとサーバーがリソースを共有できるためです。

Tomcat と Jetty は、Spring MVC と WebFlux の両方で使用できます。ただし、それらの使用方法は大きく異なることに注意してください。Spring MVC はサーブレットブロッキング I/O に依存しており、アプリケーションが必要に応じてサーブレット API を直接使用できるようにします。Spring WebFlux は、サーブレットのノンブロッキング I/O に依存し、低レベルアダプターの背後でサーブレット API を使用します。直接使用するために公開されていません。

WebFlux アプリケーションのコンテキストでサーブレットフィルターをマップしたり、サーブレット API を直接操作したりしないことを強くお勧めします。上記の理由により、同じコンテキスト内でブロッキング I/O とノンブロッキング I/O を混在させると、実行時の問題が発生します。

パフォーマンス

パフォーマンスには多くの特徴と意味があります。一般に、リアクティブおよびノンブロッキングによってアプリケーションの実行が高速化されるわけではありません。場合によっては可能です。たとえば、WebClient を使用して リモート呼び出しを並行して実行する場合などです。ただし、ノンブロッキングな方法で処理するにはより多くの作業が必要となり、必要な処理時間がわずかに増加する可能性があります。

リアクティブでノンブロッキングの主な期待される利点は、少数の固定スレッドと少ないメモリで拡張できることです。予測可能な方法でスケーリングされるため、負荷がかかった場合でもアプリケーションの回復力が高まります。ただし、これらの利点を観測するには、ある程度の遅延(低速で予測不可能なネットワーク I/O の混在を含む)が必要です。それが、リアクティブスタックの強みを示し始めた場所であり、違いは劇的です。

同時実行モデル

Spring MVC と Spring WebFlux はどちらもアノテーション付きコントローラーをサポートしていますが、同時実行モデルと、ブロックとスレッドのデフォルトの前提には重要な違いがあります。

Spring MVC (および一般的なサーブレットアプリケーション) では、アプリケーションが現在のスレッドをブロックできることが想定されています (たとえば、リモート呼び出しの場合)。このため、サーブレットコンテナーは大きなスレッドプールを使用して、リクエスト処理中の潜在的なブロックを吸収します。

Spring WebFlux(および一般にノンブロッキングサーバー)では、アプリケーションはブロックされないと想定されています。ノンブロッキングサーバーは、小さな固定サイズのスレッドプール(イベントループワーカー)を使用してリクエストを処理します。

「スケールする」と「スレッドの数が少ない」は矛盾しているように聞こえるかもしれませんが、現在のスレッドを決してブロックしない (代わりにコールバックに依存する) ということは、吸収すべきブロック呼び出しがないため、追加のスレッドが必要ないことを意味します。

ブロッキング API の呼び出し

ブロッキングライブラリを使用する必要がある場合はどうなるでしょうか? Reactor と RxJava は両方とも、異なるスレッドで処理を続行するために publishOn オペレーターを提供します。つまり、簡単な脱出ハッチがあります。ただし、ブロッキング API はこの同時実行モデルには適していません。

可変状態

Reactor および RxJava では、演算子を使用してロジックを宣言します。実行時に、リアクティブパイプラインが形成され、データが異なるステージで順次処理されます。これの主な利点は、パイプライン内のアプリケーションコードが同時に呼び出されることがないため、アプリケーションが変更可能な状態を保護する必要がないことです。

スレッドモデル

Spring WebFlux で実行されているサーバーでどのスレッドを表示する必要がありますか?

  • 「標準的な」Spring WebFlux サーバー (たとえば、データアクセスやその他のオプションの依存関係がない) では、サーバー用に 1 つのスレッドと、リクエスト処理用に他のスレッド (通常は CPU コアの数と同じ数) が期待できます。ただし、サーブレットコンテナーは、サーブレット (ブロッキング) I/O とサーブレット 3.1 (ノンブロッキング) I/O の両方の使用をサポートするために、より多くのスレッド (たとえば、Tomcat では 10) で開始する場合があります。

  • リアクティブ WebClient は、イベントループスタイルで動作します。それに関連する少数の固定数の処理スレッド(たとえば、Reactor Netty コネクターを備えた reactor-http-nio-)を確認できます。ただし、Reactor Netty がクライアントとサーバーの両方に使用される場合、2 つはデフォルトでイベントループリソースを共有します。

  • Reactor および RxJava は、スケジューラと呼ばれるスレッドプールの抽象化を提供し、処理を別のスレッドプールに切り替えるために使用される publishOn 演算子と一緒に使用します。スケジューラーには、特定の並行性戦略を提案する名前があります。たとえば、「並列」(限られた数のスレッドを使用する CPU バウンド作業の場合)または「エラスティック」(多数のスレッドを使用する I/O バウンド作業の場合)このようなスレッドが表示される場合は、一部のコードが特定のスレッドプール Scheduler 戦略を使用していることを意味します。

  • データアクセスライブラリおよびその他のサードパーティの依存関係も、独自のスレッドを作成して使用できます。

設定

Spring Framework は、サーバーの起動と停止をサポートしません。サーバーのスレッドモデルを構成するには、サーバー固有の構成 API を使用するか、Spring Boot を使用する場合は各サーバーの Spring Boot 構成オプションを確認する必要があります。WebClient を直接設定できます。他のすべてのライブラリについては、それぞれのドキュメントを参照してください。