Spring Web Reactive | 1. Spring WebFlux | 1.1. 概要

Spring WebFluxはなぜ作られたのでしょうか。

理由の一つは、少ないスレッドとハードウェア資源で同時実行を処理するために、ノンブロッキングWebスタックが必要だったことです。Servlet 3.1はノンブロッキングI/O APIを提供しましたが、同期的なFilterServlet、ブロッキングするgetParametergetPartとは異なります。Nettyなどのノンブロッキングランタイムに共通する新しいAPIが必要になりました。

もう一つの理由は関数型プログラミングです。Java 5のアノテーションとJava 8のラムダ式により、CompletableFutureReactiveXのように非同期ロジックを宣言的に構成できます。Spring WebFluxはアノテーション付きコントローラーに加えて関数型Webエンドポイントも提供します。

1.1.1. Reactiveの定義

Reactiveとは変化に反応するプログラミングモデルです。ノンブロッキング処理は、待機せずに処理完了やデータ到着の通知に反応します。

重要な仕組みの一つがノンブロッキングのバックプレッシャーです。同期コードではブロッキング呼び出しが自然に呼び出し元を待機させます。ノンブロッキングコードでは、高速なproducerがconsumerを圧倒しないようにイベント速度を制御する必要があります。

Reactive Streamsはバックプレッシャーを持つ非同期コンポーネント間の対話を定義する小さな仕様で、Java 9にも採用されています。例えば、Publisherであるデータストアは、SubscriberであるHTTPサーバーが応答へ書き込めるデータを生成します。

FAQ: Publisherが速度を落とせない場合はどうしますか。
Reactive Streamsは仕組みと境界だけを定義します。バッファリング、破棄、失敗のいずれかを選択する必要があります。

1.1.2. Reactive API

Reactive Streamsは相互運用性に重要ですが、アプリケーションAPIとしては低水準です。アプリケーションには、コレクションに限らず非同期ロジックを構成できる豊富なAPIが必要です。

ReactorはSpring WebFluxが採用するリアクティブライブラリです。MonoFluxは0..1と0..Nのシーケンスを扱い、ReactiveXのoperator vocabularyに対応する演算子を提供します。

WebFluxは内部でReactorを必要としますが、Reactive Streamsを介して他のライブラリと相互運用できます。一般的なPublisherを入力として受け取り、内部でReactor型へ変換し、FluxまたはMonoを返します。詳細はReactive Librariesを参照してください。

WebFluxはKotlin Coroutinesもサポートします。詳細はCoroutines APIを参照してください。

1.1.3. プログラミングモデル

spring-webモジュールはHTTP抽象化、Reactive Streamsアダプター、codec、WebHandler APIを提供します。WebFluxには二つのモデルがあります。

  • アノテーション付きコントローラー: Spring MVCと同じアノテーションを使用します。MVCとWebFluxはリアクティブな戻り値をサポートし、WebFluxはリアクティブな@RequestBody引数もサポートします。
  • 関数型エンドポイント: ラムダ式を使用する軽量モデルです。アプリケーションがルーティングと処理を直接担当します。

1.1.4. 適用性

Spring MVCとWebFluxは併用でき、一貫性を保つように設計されています。

spring-mvc-and-webflux-venn

  • 動作しているMVCアプリケーションは、理由がなければ変更不要です。命令型コードは理解しやすく、多くのライブラリはブロッキングです。
  • ノンブロッキングスタックが必要ならWebFluxが適しています。Netty、Tomcat、Jetty、Undertow、Servlet 3.1+と複数のリアクティブライブラリを選択できます。
  • 関数型エンドポイントは軽量なJava 8ラムダまたはKotlinのWebフレームワーク、小規模アプリケーション、マイクロサービスに適しています。
  • マイクロサービスではMVC、WebFluxコントローラー、関数型エンドポイントを混在できます。
  • JPA、JDBC、ネットワークAPIなどのブロッキングAPIへ依存する場合、通常はMVCが適しています。
  • MVCでもリアクティブなWebClientを利用し、コントローラーからリアクティブ型を返せます。
  • 移行には学習コストがあります。まずWebClientから小さく始め、効果を測定してください。

1.1.5. サーバー

WebFluxは共通の低水準APIを介してTomcat、Jetty、Servlet 3.1+、Netty、Undertowをサポートします。

Spring Framework自身はサーバーを起動、停止しませんが、アプリケーションを簡単に構成して実行できます。Spring Bootはこれを自動化し、標準でNettyを使用します。

TomcatとJettyはMVCとWebFluxの両方に対応します。MVCはブロッキングServlet I/O、WebFluxはアダプターの背後でServlet 3.1のノンブロッキングI/Oを使用します。UndertowではServlet APIを介さずUndertow APIを直接使用します。

1.1.6. パフォーマンス

リアクティブなノンブロッキングアプリケーションが常に高速になるわけではありません。主な利点は少数の固定スレッドと少ないメモリで拡張でき、負荷時の回復力が高まることです。この利点は遅延や予測しにくいネットワークI/Oを含む処理で明確になります。

1.1.7. 同時実行モデル

MVCは現在のスレッドがブロックされることを想定し、大きなスレッドプールを使います。WebFluxはブロックしないことを想定し、少数の固定イベントループworkerを使います。

ブロッキング呼び出しを吸収する追加スレッドが不要なため、少ないスレッドで拡張できます。

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

ReactorとRxJavaは別スレッドへ処理を切り替えるpublishOn演算子を提供します。ただし、ブロッキングAPIはこのモデルに適しません。

可変状態

ReactorとRxJavaの演算子はデータを段階的に処理するパイプラインを作ります。パイプライン内のコードは同時に呼び出されないため、可変状態を保護する必要が減ります。

スレッドモデル

  • 基本的なWebFluxサーバーは通常、一つのサーバースレッドとCPUコア数程度の処理スレッドを使います。Servletコンテナーはより多くのスレッドで起動する場合があります。
  • リアクティブWebClientは固定数のイベントループスレッドを使います。Reactor Nettyのclientとserverは標準で資源を共有します。
  • ReactorとRxJavaのschedulerはスレッドプールを抽象化します。publishOnでCPU-bound向けのparallel、I/O-bound向けのelasticなどへ切り替えます。
  • データアクセスライブラリなども独自のスレッドを作成できます。

設定

サーバー固有APIまたはSpring Bootオプションでサーバーを設定します。WebClientbuilderで直接設定できます。