はじめに
Spring WebFluxは、Reactive Streams API上に構築されたリアクティブスタックWebアプリケーションをサポートします。
ここでは、以下の公式ドキュメントから、要旨を整理します。
Spring WebFluxの位置付け
従来より、Springフレームワークに含まれているWebフレームワークであるSpring WebMVCは、サーブレットAPIおよびサーブレットコンテナ専用に構築されました。
Spring WebFluxリアクティブスタックWebフレームワークです。完全にノンブロッキングであることが謳われており、 Reactive Streamsのバックプレッシャをサポートしています。Netty、UndertowやServlet 3.1以降のサーブレットコンテナで実行可能です。
どちらのWebフレームワークも、SpringFrameworkに共存しています。アプリケーションは、どちらか一方のモジュール、または場合によっては両方を使用できます。
概要
Spring WebFluxが導入された理由
非ブロッキングWebスタックの必要性
少数のスレッドで同時実行を処理し、より少ないハードウェアリソースで拡張するための非ブロッキングWebスタックの必要性があります。
サーブレット3.1は、非ブロッキングI/O用のAPIを提供しました。ただし、これを使用すると、コントラクトが同期(Filter、Servlet)またはブロッキング(getParameter、 getPart)であるサーブレットAPIの残りの部分から乖離します。これが、新しい共通APIが非ブロッキングランタイム全体の基盤として機能する動機でした。
非同期の非ブロッキングスペースで十分に確立されているサーバー(Nettyなど)があるため、これは重要です。
関数型プログラミング
Java 5でのアノテーションの追加により、(アノテーション付きのRESTコントローラーやユニットテストなどの)機会が生まれたのと同様に、Java 8でのラムダ式の追加により、Javaで機能するAPIの機会が生まれました。これは、非同期ロジックの宣言型構成を可能にするノンブロッキングアプリケーションと継続型API(CompletableFutureおよびReactiveXによって普及している)にとっての恩恵です。
プログラミングモデルレベルでは、Java8によりSpring WebFluxは、注釈付きコントローラーとともに機能的なWebエンドポイントを提供できるようになりました。
「リアクティブ」の定義
「ノンブロッキング」と「機能」について触れましたが、そもそもリアクティブとはどういう意味でしょうか?
「リアクティブ」という用語は、変更に反応することを中心に構築されたプログラミングモデルを指します。I/Oイベントに反応するネットワークコンポーネント、マウスイベントに反応するUIコントローラーなどが、その例です。
その意味で、非ブロッキング=リアクティブです。これは、ブロックされるのではなく、操作が完了したとき、またはデータが利用可能になったときに通知に反応するモードになっているためです。
バックプレッシャ
Springチームが「反応性」と関連付けるもう1つの重要なメカニズムがあります。それは、非ブロッキングのバックプレッシャです。同期の命令型コードでは、呼び出しをブロックすることは、呼び出し元を待機させる自然な形のバックプレッシャとして機能します。非ブロッキングコードでは、高速プロデューサーが宛先を圧倒しないように、イベントのレートを制御することが重要になります。
Reactive Streams
Reactive Streamsは、 バックプレッシャを使用した非同期コンポーネント間の相互作用を定義する小さな仕様 (Java 9でも採用)です。たとえば、データリポジトリ( パブリッシャーとして機能)は、HTTPサーバー( サブスクライバーとして機能)が応答に書き込むことができるデータを生成できます。Reactive Streamsの主な目的は、サブスクライバーがパブリッシャーがデータを生成する速度を制御できるようにすることです。
ここで、パブリッシャーが速度を落とせない場合はどうなるか?という疑問が発生します。
パブリッシャーが速度を落とすことができない場合は、バッファーするか、ドロップするか、失敗するかを決定する必要があります。
Reactive Streamsの目的は、メカニズムと境界を確立することまでです。
リアクティブAPI
Reactive Streamsは、相互運用性にとって重要な役割を果たします。ライブラリやインフラストラクチャコンポーネントには関心がありますが、レベルが低すぎるため、アプリケーションAPIとしてはあまり役立ちません。アプリケーションは、非同期ロジックを構成するために、より高レベルでよりリッチで機能的なAPIを必要とします。これは、Java 8 Stream APIに似ていますが、コレクションだけではありません。これは、リアクティブライブラリが果たす役割です。
Reactorは、Spring WebFluxに最適なリアクティブライブラリです。演算子のReactiveX語彙に合わせた豊富な演算子のセットを介して、0..1()および0..N( )のデータシーケンスを処理するためのMono
および Flux
APIタイプを提供します。ReactorはReactive Streamsライブラリであるため、そのすべてのオペレーターはノンブロッキングバックプレッシャをサポートしています。Reactorは、サーバー側のJavaに重点を置いています。Springとの緊密なコラボレーションにより開発されました。MonoFlux
WebFluxは依存関係としてReactorを必要としますが、Reactive Streamsを介して他のリアクティブライブラリと相互運用が可能です。原則として、WebFlux APIはプレーンPublisher を入力として受け入れ、それを内部でReactorタイプに適合させ、それを使用して、出力としてFlux
またはMono
いずれかを返します。したがって、任意のPublisherを入力として渡すことができ、出力に操作を適用できますが、別のリアクティブライブラリで使用できるように出力を調整する必要があります。可能な場合はいつでも(たとえば、注釈付きコントローラー)、WebFluxはRxJavaまたは別のリアクティブライブラリの使用に透過的に適応します。
Reactive APIに加えて、WebFluxは KotlinのコルーチンAPIでも使用でき、より必須のプログラミングスタイルを提供します。次のKotlinコードサンプルは、コルーチンAPIで提供されます。
プログラミングモデル
このモジュールには、HTTP抽象化、サポートされているサーバー用のReactive Streamsアダプター、コーデック、サーブレットAPIに匹敵するが非ブロッキングコントラクトを備えたコアAPIspring-webなど、SpringWebFluxの基盤となるリアクティブ基盤が含まれています。WebHandler
その基盤の上に、SpringWebFluxは2つのプログラミングモデルの選択肢を提供します。
アノテーション付きコントローラー Annotated Controllers:
Spring MVCと整合性があり、モジュールからの同じアノテーションに基づいています。Spring MVCとWebFluxコントローラーはどちらもリアクティブ(ReactorとRxJava)の戻り型をサポートしているため、それらを区別するのは簡単ではありません。注目すべき違いの1つは、WebFluxがリアクティブ@RequestBody引数もサポートしていることです。
関数型エンドポイント:
ラムダベースの軽量で関数型プログラミングモデル。これは、アプリケーションがリクエストをルーティングおよび処理するために使用できる小さなライブラリまたはユーティリティのセットと考えることができます。アノテーション付きコントローラーとの大きな違いは、アプリケーションが最初から最後までリクエスト処理を担当するのに対し、アノテーションを介してインテントを宣言してコールバックされることです。
適用性
Spring MVCまたはWebFlux?
自然な質問ですが、これは不健全な二分法を設定する質問です。実際には、両方が連携して、利用可能なオプションの範囲を拡大します。この2つは、相互の継続性と一貫性を保つように設計されており、同時に使用でき、それぞれの側からのフィードバックが両方の側に役立ちます。次の図は、2つの関係、共通点、およびそれぞれが一意にサポートするものを示しています。
次の点を考慮することをお勧めします。
-
正常に動作するSpring MVCアプリケーションがある場合は、変更する必要はありません。命令型プログラミングは、コードを記述、理解、およびデバッグするための最も簡単な方法です。歴史的に、ほとんどがブロックしているので、ライブラリの選択肢は最大限にあります。
-
ノンブロッキングWebスタックをすでに購入している場合、Spring WebFluxは、この分野の他のサーバーと同じ実行モデルの利点を提供し、サーバー(Netty、Tomcat、Jetty、Undertow、およびServlet 3.1以降のコンテナー)の選択肢も提供します。プログラミングモデル(注釈付きコントローラーと機能するWebエンドポイント)の選択、およびリアクティブライブラリ(Reactor、RxJava、またはその他)の選択。
-
Java 8ラムダまたはKotlinで使用するための軽量で機能的なWebフレームワークに関心がある場合は、SpringWebFlux機能Webエンドポイントを使用できます。これは、透明性と制御性の向上から恩恵を受けることができる、要件がそれほど複雑でない小規模なアプリケーションやマイクロサービスにも適しています。
-
マイクロサービスアーキテクチャでは、Spring MVCまたはSpring WebFluxコントローラー、あるいはSpring WebFlux機能エンドポイントのいずれかを使用してアプリケーションを組み合わせることができます。両方のフレームワークで同じ注釈ベースのプログラミングモデルをサポートすることで、適切な仕事に適切なツールを選択しながら、知識を再利用することが容易になります。
-
アプリケーションを評価する簡単な方法は、その依存関係を確認することです。ブロッキング永続API(JPA、JDBC)またはネットワーキングAPIを使用する場合は、少なくとも一般的なアーキテクチャーにはSpring MVCが最適です。ReactorとRxJavaの両方で、別のスレッドでブロッキング呼び出しを実行することは技術的に実現可能ですが、非ブロッキングWebスタックを最大限に活用することはできません。
-
リモートサービスへの呼び出しを伴うSpring MVCアプリケーションがある場合は、リアクティブを試してくださいWebClient。Spring MVCコントローラーメソッドから直接リアクティブタイプ(Reactor、RxJava、またはその他)を返すことができます。コールあたりの遅延またはコール間の相互依存性が高いほど、メリットは劇的になります。Spring MVCコントローラーは、他のリアクティブコンポーネントも呼び出すことができます。
サーバー
Spring WebFluxは、Tomcat、Jetty、サーブレット3.1以降のコンテナ、およびNettyやUndertowなどのサーブレット以外のランタイムでサポートされています。すべてのサーバーは低レベルの 共通APIに適合しているため、サーバー間で高レベルの プログラミングモデルをサポートできます。
Spring WebFluxには、サーバーを起動または停止するためのサポートが組み込まれていません。ただし、Spring構成と WebFluxインフラストラクチャからアプリケーションをアセンブルし、数行のコードで実行するのは簡単です。
Spring Bootには、これらの手順を自動化するWebFluxスターターがあります。デフォルトでは、スターターはNettyを使用しますが、MavenまたはGradleの依存関係を変更することで、Tomcat、Jetty、またはUndertowに簡単に切り替えることができます。
Spring Bootは、非同期の非ブロッキングスペースでより広く使用され、クライアントとサーバーがリソースを共有できるようにするため、デフォルトでNettyに設定されています。
TomcatとJettyは、Spring MVCとWebFluxの両方で使用できます。ただし、それらの使用方法は大きく異なることに注意してください。Spring MVCはサーブレットブロッキングI/Oに依存しており、アプリケーションが必要に応じてサーブレットAPIを直接使用できるようにします。Spring WebFluxは、サーブレット3.1の非ブロッキングI/Oに依存し、低レベルのアダプターの背後でサーブレットAPIを使用します。直接使用するために公開されていません。
Undertowの場合、Spring WebFluxはサーブレットAPIを用いず、Undertow APIを直接使用します。
パフォーマンス
パフォーマンスには多くの特徴と意味があります。
通常、リアクティブ/非ブロッキングでは、アプリケーションの実行速度は向上しません。場合によっては、(たとえば、WebClientを使用してリモート呼び出しを並行して実行する場合)可能です。全体として、非ブロッキング方式で処理を行うにはより多くの作業が必要であり、必要な処理時間がわずかに長くなる可能性があります。
リアクティブでノンブロッキングの主な期待される利点は、少数の固定数のスレッドと少ないメモリでスケーリングできることです。
これにより、アプリケーションはより予測可能な方法で拡張できるため、負荷がかかった状態でのアプリケーションの復元力が高まります。ただし、これらの利点を確認するには、ある程度の遅延が必要です(低速で予測不可能なネットワークI/Oの組み合わせを含む)。ここでリアクティブスタックがその強みを発揮し始め、その違いは劇的なものになる可能性があります。
並行性モデル
Spring MVCとSpring WebFluxはどちらも注釈付きコントローラーをサポートしていますが、同時実行モデルと、ブロッキングとスレッドのデフォルトの仮定には重要な違いがあります。
-
Spring MVC(および一般的なサーブレットアプリケーション)では、アプリケーションが現在のスレッドをブロックできると想定されています(たとえば、リモート呼び出しの場合)。このため、サーブレットコンテナは大きなスレッドプールを使用して、リクエスト処理中の潜在的なブロッキングを吸収します。
-
Spring WebFlux(および一般に非ブロッキングサーバー)では、アプリケーションはブロッキングしないと想定されています。したがって、非ブロッキングサーバーは、小さな固定サイズのスレッドプール(イベントループワーカー)を使用して要求を処理します。
「スケーリングする」と「スレッドの数が少ない」は矛盾しているように聞こえるかもしれませんが、現在のスレッドをブロックしない(そして代わりにコールバックに依存する)ということは、吸収するためのブロック呼び出しがないため、余分なスレッドが必要ないことを意味します。
ブロッキングAPIの呼び出し
ブロッキングライブラリを使用する必要がある場合はどうなりますか?ReactorとRxJavaはどちらも publishOn、別のスレッドで処理を続行するためのオペレーターを提供します。つまり、簡単な脱出用ハッチがあります。ただし、ブロッキングAPIはこの同時実行モデルには適していません。
可変状態
ReactorとRxJavaでは、演算子を使用してロジックを宣言します。実行時に、データが個別の段階で順次処理されるリアクティブパイプラインが形成されます。これの主な利点は、パイプライン内のアプリケーションコードが同時に呼び出されることがないため、アプリケーションが可変状態を保護する必要がなくなることです。
スレッドモデル
Spring WebFluxで実行されているサーバーでどのスレッドが表示されると予想しますか?
「バニラ」のSpringWebFluxサーバー(たとえば、データアクセスやその他のオプションの依存関係なし)では、サーバー用に1つのスレッド、要求処理用に他のいくつかのスレッド(通常はCPUコアの数と同じ数)を期待できます。ただし、サーブレットコンテナは、サーブレット(ブロッキング)I/Oとサーブレット3.1(非ブロッキング)I/Oの両方の使用をサポートするために、より多くのスレッド(たとえば、Tomcatでは10)で開始する場合があります。
リアクティブWebClientはイベントループスタイルで動作します。そのため、それに関連する少数の固定数の処理スレッドを確認できます(たとえば、reactor-http-nio-Reactor Nettyコネクタを使用)。ただし、Reactor Nettyがクライアントとサーバーの両方に使用されている場合、デフォルトでは2つの共有イベントループリソースが使用されます。
ReactorとRxJavaは、スケジューラーと呼ばれるスレッドプールの抽象化を提供し、 publishOn処理を別のスレッドプールに切り替えるために使用される演算子で使用します。スケジューラーには、特定の並行性戦略を示唆する名前があります。たとえば、「並列」(限られた数のスレッドでのCPUバウンド作業の場合)または「弾性」(多数のスレッドでのI/Oバウンド作業の場合)です。このようなスレッドが表示された場合は、一部のコードが特定のスレッドプールScheduler戦略を使用していることを意味します。
データアクセスライブラリやその他のサードパーティの依存関係も、独自のスレッドを作成して使用できます。
構成
Spring Frameworkは、サーバーの起動と停止をサポートしていません 。サーバーのスレッドモデルを構成するには、サーバー固有の構成APIを使用する必要があります。
関連情報